1. 程式人生 > >AOP加自定義註解實現redis註解快取

AOP加自定義註解實現redis註解快取

  很早之前學習了redis,在這次畢業設計中使用redis對熱點資料進行快取,其中使用spring-data-redis還有@Cacheable、@CachePut和@CacheEvict這3個註解。註解的方式避免了在業務方法中參雜太多非業務的邏輯程式碼,比如日誌和事務,都會用AOP配合註解把共用部分抽離出來,同時業務方法也更純粹簡潔。在使用時發現,spring-data-redis雖是強大,但是許多功能用不上,我只需要簡單的快取新增、刪除功能,於是基於自定義註解和AOP知識實現了3個快取註解。

關鍵點

1、@Around("@annotation(myCacheAnnotation.Cache)")
,使用spring的@Around註解,對我自定義的註解類Cache進行切入,也就是說,凡是使用到@Cache這個註解的方法,執行時會先執行@Around下的方法,這樣就可以把之前寫在業務方法中的快取邏輯移動到這裡,比如獲取資料前先到redis伺服器獲取快取,快取不存在再到資料庫中去獲取;
2、Method m = ((MethodSignature) pjp.getSignature()).getMethod(); 這行程式碼中的m是代理物件,沒有包含原方法上的註解;Method methodWithAnnotations = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes());
ProceedingJoinPoint(上面的pjp)的this()返回spring生成的代理物件,target()返回被代理的目標物件,目標物件反射獲取的method物件才包含註解;
3、java本身的反射功能中不能獲取方法引數名,藉助LocalVariableTableParameterNameDiscoverer類可以根據一個Method物件獲取其方法引數名。

核心程式碼

AOP切面類

package myCacheAnnotation;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import service.IUserService;

import java.lang.reflect.Method;

/**
 * writer: holien
 * Time: 2018-01-18 11:20
 * Intent: aop配合cache註解實現快取
 */
@Component
@Aspect
public class CacheAspect {

    @Autowired
    private JedisPool jedisPool;

    // 在使用Cache註解的地方切入此切點
    @Around("@annotation(myCacheAnnotation.Cache)")
    private Object handleCache(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("以下是快取邏輯");
        // 獲取切入的方法物件
        // 這個m是代理物件的,沒有包含註解
        Method m = ((MethodSignature) pjp.getSignature()).getMethod();
        // this()返回代理物件,target()返回目標物件,目標物件反射獲取的method物件才包含註解
        Method methodWithAnnotations = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes());
        // 根據目標方法物件獲取註解物件
        Cache cacheAnnotation = methodWithAnnotations.getDeclaredAnnotation(myCacheAnnotation.Cache.class);
        // 解析key
        String keyExpr = cacheAnnotation.key();
        Object[] as = pjp.getArgs();
        String key = parseKey(methodWithAnnotations, as, keyExpr);
        // 註解的屬性本質是註解裡的定義的方法
//        Method methodOfAnnotation = a.getClass().getMethod("key");
        // 註解的值本質是註解裡的定義的方法返回值
//        String key = (String) methodOfAnnotation.invoke(a);
        // 到redis中獲取快取
        Jedis jedis = jedisPool.getResource();
        String cache = jedis.get(key);
        if (cache == null) {
            // 若不存在,則到資料庫中去獲取
            Object result = pjp.proceed();
            // 從資料庫獲取後存入redis
            System.out.println("從資料庫獲取的結果以JsonString形式存入redis中");
            jedis.set(key, JSON.toJSONString(result));
            // 若有指定過期時間,則設定
            int expireTime = cacheAnnotation.expire();
            if (expireTime != -1) {
                jedis.expire(key, expireTime);
            }
            return result;
        } else {
            return JSON.parse(cache);
        }
    }

    // 引數2為方法引數值,引數3為註解中某個屬性的值,若含有#則為一個表示式
    // 待解決,解析#user.id的問題
    private String parseKey(Method method, Object[] argValues, String expr) {
        if (expr.contains("#")) {
            String paramName = expr.substring(expr.indexOf('#') + 1);
            // 獲取方法引數名列表
            LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
            String[] paramNames = discoverer.getParameterNames(method);
            for (int i = 0; i < paramNames.length; i++) {
                if (paramNames[i].equals(paramName)) {
                    return expr.substring(0, expr.indexOf('#')) + argValues[i].toString();
                }
            }
            throw new IllegalArgumentException("解析不了該引數,錯誤引數表示式");
        } else {
            // 不需要解析,直接返回
            return expr;
        }
    }

    // 待解決,解析#user.id的問題
    @Around("@annotation(myCacheAnnotation.CachePut)")
    private Object handleCachePut(ProceedingJoinPoint pjp) throws Throwable {
        return null;
    }

    @Around("@annotation(myCacheAnnotation.CacheEvict)")
    private Object handleCacheEvict(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("以下是刪除快取邏輯");
        // 獲取切入的方法物件
        // 這個m是代理物件的,沒有包含註解
        Method m = ((MethodSignature) pjp.getSignature()).getMethod();
        // this()返回代理物件,target()返回目標物件,目標物件反射獲取的method物件才包含註解
        Method methodWithAnnotations = pjp.getTarget().getClass().getDeclaredMethod(pjp.getSignature().getName(), m.getParameterTypes());
        // 根據目標方法物件獲取註解物件
        CacheEvict cacheEvictAnnotation = methodWithAnnotations.getDeclaredAnnotation(myCacheAnnotation.CacheEvict.class);
        // 解析key
        String keyExpr = cacheEvictAnnotation.key();
        Object[] as = pjp.getArgs();
        String key = parseKey(methodWithAnnotations, as, keyExpr);
        // 先刪除資料庫中的使用者資訊再刪除快取
        Object result = pjp.proceed();
        Jedis jedis = jedisPool.getResource();
        jedis.del(key);
        System.out.println("刪除快取中的使用者資訊");
        return result;
    }

    public static void main(String[] args) throws Exception {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        IUserService userService = (IUserService) context.getBean("userService");
//        System.out.println(userService.getUserInfo(10));
        userService.deleteUserInfo(10);
    }

}

Cache註解類

package myCacheAnnotation;

import java.lang.annotation.*;

/**
 * writer: holien
 * Time: 2018-01-18 10:49
 * Intent: 自定義快取註解(String、hash)
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Cache {
    String key();
    int expire() default -1;
}

不足與補充

此註解還可以使用Guava包中的布隆過濾器,對資料庫和快取中都不存在的查詢放進過濾器,防止快取擊穿攻擊;本來想借助SpEL來解析註解引數值,但是沒有試驗成功,只好自己寫了個簡單的對#XXX格式的引數進行解析,XXX只能是方法的其中一個引數名,不能是引數的屬性或方法返回值,即不能解析#user.id或#user.getId();

總結
其實重複造輪子是沒有必要的,但是以學習或特定業務為目的造個小輪子是值得的,這次的學習也讓我體會到AOP和註解的強大之處,站在偉人的肩膀上看得更遠。


相關推薦

AOP定義註解實現redis註解快取

  很早之前學習了redis,在這次畢業設計中使用redis對熱點資料進行快取,其中使用spring-data-redis還有@Cacheable、@CachePut和@CacheEvict這3個註解。註解的方式避免了在業務方法中參雜太多非業務的邏輯程式碼,比如日誌和事務,

使用Spring AOP結合定義Java註解實現動態資料來源設定

1、定義Java註解 @Retention(RetentionPolicy.RUNTIME) // 註解將要寫到型別(Class/Interface)還是其它元素(Method等)上,支援package、type、method、field等,一般只會配置一個@Target

Spring Boot系列——AOP定義註解的最佳實踐

AOP(Aspect Oriented Programming),即面向切面程式設計,是Spring框架的大殺器之一。 首先,我宣告下,我不是來系統介紹什麼是AOP,更不是照本宣科講解什麼是連線點、切面、通知和切入點這些讓人頭皮發麻的概念。 今天就來說說AOP的一些應用場景以及如何通過和其他特性的結合提升

定義實現SpringMvc框架,定義@Controller、@RequestMapping註解,自己也是一步一步的對程式碼的理解出來的,只是比較簡單的例子

1.自定義的DispatcherServlet,就是SpringMvc執行載入類 /*** * 手寫SpringMvc框架<br> * 思路:<br> * 1.手動建立一個DispatcherServlet 攔截專案的所有請求 SpringMv

AOP攔截定義註解並獲取註解屬性與上下文引數(基於Springboot框架)

目錄 AOP可以用於日誌的設計,這樣話就少不了要獲取上下文的資訊,博主在設計日誌模組時考慮了一下此法,整理了一下如何用AOP來攔截你自定義的註解。 自定義註解 首先先自定義一個註解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNT

利用AOP定義註解進行Token校驗

因為公司業務需要每個訪問的方法校驗token,程式碼比較重複,所以就考慮利用Spring的AOP對每個方法進行token校驗。運用到了aop和自定義註解的知識。aop配置檔案 <!-- 自定義註解 --> <bean id="authTok

使用Spring Boot的AOP處理定義註解

前言 上一篇文章Java 註解介紹講解了下Java註解的基本使用方式,並且通過自定義註解實現了一個簡單的測試工具;本篇文章將介紹如何使用Spring Boot的AOP來簡化處理自定義註解,並將通過實現一個簡單的方法執行時間統計工具為樣例來講解這些內容。

spring aop攔截定義註解的切入點表示式

@within(com.cxh.study.aop.controller.UserAccessAnnotation) 表示攔截含有com.cxh.study.aop.controller.UserAccessAnnotation這個註解的類中所有方法 @an

定義test之dubbo註解實現

       本文是作者在使用dubbo開發的時候,在使用Junit寫單元測試時,對於dubbo服務的消費者如果獲取呢,最常用就是在測試類上加註解 @ContextConfiguration("/sp

Spring AOP 結合定義註解的使用

for img support bsp parser disco get asp src 例如要實現一個評論內容的攔截 1,聲明自定義註解,這裏的key()為要攔截的方法中的方法體對應的變量名,可以參考第3點. 2,創建一個切面類,@annatation()中的co

SpringBoot框架:通過AOP定義註解完成druid連線池的動態資料來源切換(三)

一、引入依賴   引入資料庫連線池的依賴——druid和麵向切面程式設計的依賴——aop,如下所示: <!-- druid --> <dependency> <groupId

ckeditor添定義按鈕整合swfupload實現批量上傳圖片

下載 了解 nbsp 文件 mouseover 去掉 dial size pro ckeditor添加自定義按鈕整合swfupload實現批量上傳圖片給ckeditor添加自定義按鈕,由於ckeditor只能上傳一張圖片,如果要上傳多張圖片就要結合ckfinder,而ckf

定義 Java Annotation ,讀取註解

sta tools stp exc num value mage test lang 1. 首先是自定義註解: Java代碼 package cn.veji.hibernate.po; import java.lang.annot

定義spring參數註解 - 打破@RequestBody單體限制

hand 動態 except builder 替換 true delattr actor 對象 本文主要描述怎樣自定義類似@RequestBody這樣的參數註解來打破@RequestBody的單體限制。 目錄1 @RequestBody的單體限制2 自定義spring的

SpringBoot中如何定義自己的Valid註解

在專案的開發中,在後臺介面中,我們經常需要對前端傳來的資料進行校驗,一般都是使用@Valid註解,但是有的時候我們的校驗工作比較複雜,@Valid提供的註解無法滿足我們的需求,這個時候我們就需要自己來定義我們自己所需的Valid註解。有同學不清楚@Valid註解的可以看一下我的另一篇文章Spring

SpringMVC Hibernate validator使用以及定義校驗器註解

Hibernate validator使用以及自定義校驗器註解 Hibernate Validator常用註解 1.建立自定義校驗器 import javax.validation.Constraint; import javax.validation.Payloa

定義LinearLayout實現減以及長按持續增加和減少

public class UpDownChooseView extends LinearLayout implements View.OnClickListener{ private Context context; private FrameLayout

javaWeb定義可排序過濾器註解,解決Servlet3.0下@WebFilter註解無法排序問題

com.lwl.anno 註釋型別 @WebFilterSort 需要用的jar包 http://download.csdn.net/detail/u013202238/9431110 用該註解註冊的Filter預設在部署描述

從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP定義篩選,Redis入門 11.1

大神留步 先說下一個窩心的問題,求大神幫忙,如何在AOP中去使用泛型,有償幫助,這裡謝謝,文末有詳細問題說明,可以留言或者私信都可以。 當然我也會一直思考,大家持續關注本帖,如果我想到好辦法,會及時更新,並通知大家。 程式碼已上傳Github+Gitee,文末有地址   傳統的快取是在Co

core學習歷程五 從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十 || AOP面向切面程式設計淺解析:簡單日誌記錄 + 服務切面快取 從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP自定義篩選,Redis入門 11.1

繼續學習 “老張的哲學”博主的系列教程,感謝大神們的無私分享 從壹開始前後端分離【 .NET Core2.0 +Vue2.0 】框架之十 || AOP面向切面程式設計淺解析:簡單日誌記錄 + 服務切面快取 說是朦朧,,emmm,對我來說是迷糊哈。上半段正常,下半段有點難理解,操作是沒問題。多看幾遍再消