項目-無侵入代碼方式使用Redis實現緩存功能

前言

近期有同學問我,怎麼使用aop方式用redis爲項目接口調用添加緩存,在這裏總結整理一下,博文難免會有紕漏,如有問題請評論不吝告知。

在本文章,你會了解到如何使用redis,以及如何通過jedis操作redis通過AOP的方式實現緩存。在文章後面還介紹了AOP的相關知識點,希望對大家有些許幫助~

如果轉載此博文,請附上本文鏈接,謝謝合作~
如果感覺這篇文章對您有所幫助,請“點贊”或者“關注”博主,您的喜歡和關注將是我前進的最大動力~

一:環境準備

1:準備Redis環境

使用redis做緩存的話,需要有redis服務,可以將服務部署在遠程服務器上,也可以部署到本機上。

1.1. 部署在linux服務器

1.1.1安裝Redis

#安裝redis,當前最新的版本是redis-5.0.0.tar.gz,可以通過http://download.redis.io/releases地址查看最新版本
$ wget http://download.redis.io/releases/redis-5.0.0.tar.gz
$ tar xzf redis-5.0.0.tar.gz
$ cd redis-5.0.0
$ make

1.1.2啓動Redis服務並使用

 #啓動redis服務
$ cd src
$ ./redis-server
#使用redis客戶端測試redis
$ cd src
$ ./redis-cli
redis> set testkey  testvalue
OK
redis> get testkey
"testvalue"

如果上述過程沒有報錯的話,那麼恭喜你啓動redis服務成功,下面我們將會使用jedis操作redis來實現緩存

1.2. 部署在windows服務器

2.1下載redis壓縮包

下載zip壓縮包(Redis-x64-*.zip):https://github.com/MSOpenTech/redis/releases
將其解壓到某一文件夾中,重命名爲Redis

2.2啓動redis服務並使用

打開cmd,切換到解壓的Redis文件夾中,運行如下命令,
會發現出現”The server is now ready to accept connections on port 6379“字樣表示啓動成功

redis-server.exe redis.windows.conf

再打開一個cmd,原來的cmd不要關閉,保持打開狀態,輸入以下命令:
其中:127.0.0.1:爲你的redis服務ip地址,如果是本機安裝的就是127.0.0.1,端口6379是redis默認監聽的端口

redis-cli.exe -h 127.0.0.1 -p 6379
#如果redis設置了密碼,可以添加參數-a指定密碼,例如:
redis-cli.exe -h 127.0.0.1 -p 6379 -a 12345

可以使用redis命令測試是否可以正常使用,至此redis服務便準備完畢了~

2:準備項目環境

  1. 首先spring boot項目,當然不是boot項目也可以,我是以boot項目舉例的
  2. pom文件添加依賴,只列出了此功能設計特殊所需的
    ps: 以下版本爲截止2019/10/10最新版本
		<!--jedis依賴-->
		<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!--用於序列化 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency> 
  1. application.yml添加配置,如果你是xml格式的文件,yml格式和xml格式類似,只是yml格式更加明瞭一些,google一下轉換一下格式就行
spring: 
  jedis:
    max:
      total: 100     #jedis總量
      idle: 50        #空閒jedis實例最大值
      waitmillis: 500   #當池內沒有返回jedis對象時,最大等待時間
      timout: 0      #當客戶端閒置多長時間後關閉連接,如果指定爲 0,表示關閉該功能,連接不會斷
    host: 127.0.0.1  # redis服務ip地址
    port: 6379        # 端口
    password: test  # redis密碼

ps : redis的常用配置參數

此處的參數需要根據想要緩存接口的調用情況進行動態配置。
至此,環境配置完成了,現在只需要操作redis實現緩存了~~

二:緩存功能實現

1:過程簡介

  1. 對於不加緩存的項目,我們每一次的請求都會去數據庫中查詢,即使兩次請求一樣並且獲取的數據一樣,也是會去查詢數據庫,這就造成了數據庫資源的浪費,並且如果併發量特別高的話,數據庫的壓力太大,容易造成查詢緩慢、數據庫宕機、查詢失敗等問題。
  2. 項目添加緩存之後,請求查詢數據的時候會先查詢緩存,緩存(這裏指只有一級緩存)中沒有才會到達數據庫。相同的請求在緩存還沒有過期 的情況下,會得到緩存中的數據並返回,不會到達數據庫,這樣做即減少了數據庫的壓力提高了併發量又提升了查詢速度。
  3. 簡易流程圖:
數據不在緩存中
數據在緩存中
請求
查詢緩存
查詢數據庫
獲得緩存中數據
返回數據
將獲得數據緩存到緩存中

2:緩存AOP實現

在使用aop之前,先大致的瞭解一下 aop:

AOP(Aspect Oriented Programing):面向切面編程,將通用的邏輯從業務邏輯中分離出來。

AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點:

  • 主要的業務處理部分。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。
  • 橫切關注點的一個特點是,他們經常發生在覈心關注點的多處,而各處都基本相似。比如權限認證、日誌、事務處理。Aop 的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。

正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業邏輯同對其提供支持的通用服務進行分離”。

AOP的底層實現主要是依賴動態代理來實現的:

  • 比如Spring aop底層使用JDK proxy(實現接口)和CGLib(繼承目標類、使用ASM庫)來生成代理類來代替目標類執行,默認使用JDK proxy ,當無接口時使用CGLib。底層採用動態代理技術(動態代理技術底層依賴的反射技術)在運行期動態生成代理類(不同於aspectJ編譯期織入代碼到目標類)。
  • 再比如AspectJ做爲java實現的統一AOP解決方案,使用ASM字節碼操作庫,需要使用特定的acj編譯器(不同於spring使用動態代理)在編譯期將代碼直接織入到目標類。

下面會詳細介紹aop相關

2.1.執行過程

  1. 請求到達Controller中的接口時,因爲我們在CacheAspect類中配置的切入點包含這個接口,所以進入CacheAspect類的doAround方法中執行緩存操作
  2. doAround中,首先獲取key,判斷redis中是否包含key,包含就返回緩存中的數據,完成請求
  3. 不包含就執行調用的接口通過查詢數據庫獲取數據,並將其緩存到redis中,完成一次請求不包含就執行調用的接口通過查詢數據庫獲取數據,並將其緩存到redis中,完成請求

2.2. 組成部分與實現

  • 自定義註解:NeedCacheAop

用在方法上面標識調用該方法的請求需要被緩存
其中的nxxx、expx、time等參數是爲了可以更靈活的空值緩存的方式與過期時間,具體含義請看下面”其他“中的set方法參數解析

/**
 * 自定義註解,用於標識方法是否需要使用緩存
 */
@Target({ElementType.PARAMETER, ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedCacheAop {
    //代表緩存策咯,nx:代表key不存在再進行緩存kv,xx:代表key存在再進行緩存kv  默認爲"不存在key緩存key"
    String nxxx() default "nx";
    //代表過期時間單位,ex:秒 px:毫秒    默認爲"秒"
    String expx() default "ex";
    //過期時間
    long time() default 30*60;
}
  • 序列化工具類:SerializeUtil

使用FastJso對要緩存的數據進行序列化後存儲與獲取緩存中的反序列化
使用fastjson對數據進行序列化與反序列化,非常簡單

public class SerializeUtil {
    private  static Logger logger = LoggerFactory.getLogger("SerializeUtil");
    public static String serializeObject(Object obj){
        logger.info("serialize object :"+obj);
        String jsonObj = JSON.toJSONString(obj);
        return jsonObj;
    }
    public static JSONObject unserializeObject(String serobj){
        logger.info("unserialize object :"+serobj);
        JSONObject jsonObj = JSON.parseObject(serobj);
        return jsonObj;
    }
}
  • 操作緩存service類:CacheService接口 與其實現類 CacheServiceImpl

方法內部封裝了關於緩存的get set containKey getKeyAop等方法

public interface CacheService {
    /**獲取jedis實例*/
    Jedis getResource() throws Exception;
    /**設置key與value*/
    void set(String key, String value, String nxxx, String expx, long time);
    /**根據key獲取value*/
    String get(String key);
    /**判斷是否存在key*/
    boolean containKey(String key);
    /**釋放jedis實例資源*/
    void returnResource(Jedis jedis);
    /**獲取key*/
    String getKeyForAop(JoinPoint joinPoint, HttpServletRequest request);
}
@Service
public class CacheServiceImpl implements CacheService {
    private static Logger logger = LoggerFactory.getLogger(CacheServiceImpl.class);

    @Autowired
    private JedisPool jedisPool;

    /**獲取jedis實例*/
    public Jedis getResource() throws Exception{
        return jedisPool.getResource();
    }

    /**設置key與value*/
    public void set(String key, String value,String nxxx,String expx,long time) {
        Jedis jedis=null;
        try{
            jedis = getResource();
            jedis.set(key,value,nxxx,expx,time);
        } catch (Exception e) {
            logger.error("Redis set error: "+ e.getMessage() +" - " + key + ", value:" + value);
        }finally{
            returnResource(jedis);
        }
    }

    /**根據key獲取value*/
    public String get(String key) {
        String result = null;
        Jedis jedis=null;
        try{
            jedis = getResource();
            result = jedis.get(key);
        } catch (Exception e) {
            logger.error("Redis set error: "+ e.getMessage() +" - " + key + ", value:" + result);
        }finally{
            returnResource(jedis);
        }
        return result;
    }

    /**判斷是否存在key*/
    public boolean containKey(String key){
        boolean b;
        Jedis jedis = null;
        try{
            jedis = getResource();
            b = jedis.exists(key);
            return b;
        }catch (Exception e){
            logger.error("Redis server error::"+e.getMessage());
            return false;
        }finally {
            returnResource(jedis);
        }
    }

    /**釋放jedis實例資源*/
    public void returnResource(Jedis jedis) {
        if(jedis != null){
            jedis.close();
        }
    }

    /**獲取key*/
    public String getKeyForAop(JoinPoint joinPoint, HttpServletRequest request){
        //獲取參數的序列化
        Object[] objects = joinPoint.getArgs();
        String args = SerializeUtil.serializeObject(objects[0]);
        //獲取請求url
        String url = request.getRequestURI();
        //獲取請求的方法
        String method = request.getMethod();
        //獲取當前日期,規避默認init情況
        String date = LocalDate.now().toString();
        //key值獲取
        return args + url + method + date;
    }
}
  • 切面類:CacheAspect

用於對相應的請求接口切入緩存存取的相關邏輯,使用AOP可以對代碼0侵入性,是一個很好的方法

@Component
@Aspect
public class CacheAspect {

    @Autowired
    CacheService cacheService;

    /**設置切入點*/
    //方法上面有@NeedCacheAop的方法,增加靈活性
    @Pointcut("@annotation(com.xcar.data.web.backend.util.annotation.NeedCacheAop)")
    public void annotationAspect(){}
    //相應包下所有以XcarIndex開頭的類中的所有方法,減少代碼侵入性
    @Pointcut("execution(public * com.xcar.data.web.backend.controller.XcarIndex*.*(..))")
    public void controllerAspect(){}

    /**環繞通知*/
    @Around(value = "controllerAspect()||annotationAspect()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        //獲取請求
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes()).getRequest();
        //存儲接口返回值
        Object object = new Object();

        //獲取註解對應配置過期時間
        NeedCacheAop cacheAop = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(NeedCacheAop.class);  //獲取註解自身
        String nxxx;String expx;long time;
        if (cacheAop == null){//規避使用第二種切點進行緩存操作的情況
            nxxx = "nx";
            expx = "ex";
            time = 30*60;  //默認過期時間爲30分鐘
        }else{
            nxxx = cacheAop.nxxx();
            expx = cacheAop.expx();
            time = cacheAop.time();
        }
        //獲取key
        String key = cacheService.getKeyForAop(joinPoint,request);
        if (cacheService.containKey(key)){
            String obj = cacheService.get(key);
            if ("fail".endsWith(obj)){  //規避redis服務不可用
                try {
                	//執行接口調用的方法
                    joinPoint.proceed();
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }
            }else{
                JSONObject klass =  SerializeUtil.unserializeObject(obj);
                return new ResponseEntity<>(klass.get("body"), HttpStatus.OK) ;
            }
        }else{
            try {
            	////執行接口調用的方法並獲取返回值
                object = joinPoint.proceed();
                String serobj = SerializeUtil.serializeObject(object);
                cacheService.set(key,serobj,nxxx,expx,time);
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        return object;
    }
}
  • jedis配置類:JedisConfiguration

用於配置JedisPool的相關參數,與創建JedisPool對象,便於後面注入使用

@Configuration
public class JedisConfiguration extends CachingConfigurerSupport {
    private Logger logger = LoggerFactory.getLogger(JedisConfiguration.class);
    @Value("${spring.jedis.port}")
    private Integer port;
    @Value("${spring.jedis.host}")
    private String host;
    @Value("${spring.jedis.max.total}")
    private Integer maxTotal;
    @Value("${spring.jedis.max.idle}")
    private Integer maxIdle;
    @Value("${spring.jedis.max.waitmillis}")
    private Long maxWaitMillis;
    @Value("${spring.jedis.password}")
    private String password;

    public JedisConfiguration() {}
    /**設置*/
    @Bean
    public JedisPool redisPoolFactory(){
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        jedisPoolConfig.setMaxTotal(maxTotal);
        JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port,1000,password);
        logger.info("JedisPool build success!");
        logger.info("Redis host:" + host + ":" + port);
        return  jedisPool;
    }
    //下面屬性是get set方法省略
 }
  • 響應數據對象:XcarIndexCarAttentionIndexResponse

響應的數據對象,緩存就是對其進行序列化後緩存
該對象類一定繼承Serializable接口,使其可被序列化,例如:

public class XcarIndexCarAttentionIndexResponse implements Serializable{
    priate List<BaseChartsResponse.Line> lines = new ArrayList<>();
    private Series_DateBubble series_datebubble = new Series_DateBubble();
    private String flag = "1";
    public Series_DateBubble getSeries_datebubble() {
        if (series_datebubble == null) {
            series_datebubble = new Series_DateBubble();
        }
        return series_datebubble;
    }
    public String getFlag() {
        return flag;
    }
    public void setFlag(String flag) {
        this.flag = flag;
    }
    public void setSeries_datebubble(Series_DateBubble series_datebubble) {
        this.series_datebubble = series_datebubble;
    }
    public List<BaseChartsResponse.Line> getLines() {
        return lines;
    }
    public void setLines(List<BaseChartsResponse.Line> lines) {
        this.lines = lines;
    }
    public class Series_DateBubble {
        private List<BaseChartsResponse.Series_DateBubble> datas = new ArrayList<>();
        private String[] dataRange = {};
        public List<BaseChartsResponse.Series_DateBubble> getDatas() {
            return datas;
        }
        public void setDatas(List<BaseChartsResponse.Series_DateBubble> datas) {
            this.datas = datas;
        }
        public String[] getDataRange() {
            return dataRange;
        }
        public void setDataRange(String[] dataRange) {
            this.dataRange = dataRange;
        }
    }
}
  • 被切入的方法:getTrendPage

我們要添加緩存的Controller接口的實現,例如:我要切入的接口

package com.xcar.data.web.backend.controller;
.....
	@RequestMapping(value = "/page/trend", method = RequestMethod.POST)
    public ResponseEntity<XcarIndexCarIntentionIndexResponse> getTrendPage(@RequestBody XcarIndexCarIntentionIndexRequest ro, HttpServletRequest request) throws Exception {
        XcarIndexCarIntentionIndexResponse res = new XcarIndexCarIntentionIndexResponse();
        try {
            res = delegate.getTrendPage(ro);
        } catch (Exception e) {
            throw e;
        }
        return new ResponseEntity(res, HttpStatus.OK);
    }

2.3.非AOP實現

在一些情況下,我們需要對方法內部一些中間查詢結果進行緩存,這樣就只能將緩存數據的代碼直接寫在方法體內,實現也相對AOP實現方式來說更加簡單,調用相關的jedis方法即可,可參考上述代碼實現。

三:知識點

1:jedis中set方法參數:

  • key :緩存的key值
  • value :緩存的value值
  • nxxx: NX|XX兩種選擇, NX – 緩存不存在時才進行緩存. XX – 緩存存在時再進行緩存
  • expx :EX|PX兩種選擇, 過期時間的代爲,EX 代表秒; PX 代表毫秒
  • time :過期時間的數值

2:AOP面向切面編程

如上述所說:

AOP(Aspect Oriented Programing):面向切面編程,將通用的邏輯從業務邏輯中分離出來。AOP把軟件系統分爲兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在覈心關注點的多處,而各處都基本相似。比如權限認證、日誌、事務處理。Aop 的作用在於分離系統中的各種關注點,將核心關注點和橫切關注點分離開來。

正如Avanade公司的高級方案構架師Adam Magee所說,AOP的核心思想就是“將應用程序中的商業邏輯同對其提供支持的通用服務進行分離”。

AOP的底層實現主要是依賴動態代理來實現的:

  • 比如Spring aop底層使用JDK proxy(實現接口)和CGLib(繼承目標類、使用ASM庫)來生成代理類來代替目標類執行,默認使用JDK proxy ,當無接口時使用CGLib。底層採用動態代理技術(動態代理技術底層依賴的反射技術)在運行期動態生成代理類(不同於aspectJ編譯期織入代碼到目標類)。
  • 再比如AspectJ做爲java實現的統一AOP解決方案,使用ASM字節碼操作庫,需要使用特定的acj編譯器(不同於spring使用動態代理)在編譯期將代碼直接織入到目標類。

AOP相關概念:

  • 連接點(Joinpoint): 表示需要在程序中插入橫切關注點的擴展點,連接點可能是類初始化、方法執行、方法調用、字段調用或處理異常等等,Spring只支持方法執行連接點;在AOP中表示爲“在哪裏幹”;
  • 切入點(Pointcut): 選擇一組相關連接點的模式,即可以認爲連接點的集合,Spring支持perl5正則表達式和AspectJ切入點模式,Spring默認使用AspectJ語法;在AOP中表示爲“在哪裏乾的集合”;
  • 通知(Advice): 在連接點上執行的行爲,通知提供了在AOP中需要在切入點所選擇的連接點處進行擴展現有行爲的手段;包括前置通知(before advice)、後置通知(after advice)、環繞通知(around advice),在Spring中通過代理模式實現AOP,並通過攔截器模式以環繞連接點的攔截器鏈織入通知;在AOP中表示爲“幹什麼”;
  • 切面(Aspect):橫切關注點的模塊化,比如日誌組件。可以認爲是通知、引入和切入點的組合;在Spring中可以使用Schema和@AspectJ方式進行組織實現;在AOP中表示爲“在哪乾和幹什麼集合”;
  • 引入(Introduction): 也稱爲內部類型聲明,爲已有的類添加額外新的字段或方法,Spring允許引入新的接口(必須對應一個實現)到所有被代理對象(目標對象);在AOP中表示爲“幹什麼(引入什麼)”;
  • 目標對象(Target Object):需要被織入橫切關注點的對象,即該對象是切入點選擇的對象,需要被通知的對象,從而也可稱爲“被通知對象”;由於Spring AOP 通過代理模式實現,從而這個對象永遠是被代理對象;在AOP中表示爲“對誰幹”;
  • AOP代理(AOP Proxy): AOP框架使用代理模式創建的對象,從而實現在連接點處插入通知(即應用切面),就是通過代理來對目標對象應用切面。在Spring中,AOP代理可以用JDK動態代理或CGLIB代理實現,而通過攔截器模型應用切面。
  • 織入(Weaving): 織入是一個過程,是將切面應用到目標對象從而創建出AOP代理對象的過程,織入可以在編譯期、類裝載期、運行期進行。組裝方面來創建一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運行時完成(jdk自帶的動態代理)。Spring和其他純Java AOP框架一樣,在運行時完成織入。

3:AOP中切點表達式

這部分內容來自該 博客

切點指示符
切點指示符是切點定義的關鍵字,切點表達式以切點指示符開始。開發人員使切點指示符來告訴切點將要匹配什麼,有以下9種切點指示符:execution、within、this、target、args、@target、@args、@within、@annotation,下面一一介結這9種切點指示符。

execution
execution是一種使用頻率比較高比較主要的一種切點指示符,用來匹配方法簽名,方法簽名使用全限定名,包括訪問修飾符(public/private/protected)、返回類型,包名、類名、方法名、參數,其中返回類型,包名,類名,方法,參數是必須的,如下面代碼片段所示:

@Pointcut(“execution(public String org.baeldung.dao.FooDao.findById(Long))”)
上面的代碼片段裏的表達式精確地匹配到FooDao類裏的findById(Long)方法,但是這看起來不是很靈活。假設我們要匹配FooDao類的所有方法,這些方法可能會有不同的方法名,不同的返回值,不同的參數列表,爲了達到這種效果,我們可以使用通配符。如下代碼片段所示:

@Pointcut(“execution(* org.baeldung.dao.FooDao.*(…))”)

第一個通配符匹配所有返回值類型,第二個匹配這個類裏的所有方法,()括號表示參數列表,括號裏的用兩個點號表示匹配任意個參數,包括0個

within
使用within切點批示符可以達到上面例子一樣的效果,within用來限定連接點屬於某個確定類型的類。如下面代碼的效果與上面的例子是一樣的:

@Pointcut(“within(org.baeldung.dao.FooDao)”)
我們也可以使用within指示符來匹配某個包下面所有類的方法(包括子包下面的所有類方法),如下代碼所示:
@Pointcut(“within(org.baeldung…*)”)

this 和 target
this用來匹配的連接點所屬的對象引用是某個特定類型的實例,target用來匹配的連接點所屬目標對象必須是指定類型的實例;那麼這兩個有什麼區別呢?原來AspectJ在實現代理時有兩種方式:

1、如果當前對象引用的類型沒有實現自接口時,spring aop使用生成一個基於CGLIB的代理類實現切面編程
2、如果當前對象引用實現了某個接口時,Spring aop使用JDK的動態代理機制來實現切面編程
this指示符就是用來匹配基於CGLIB的代理類,通俗的來講就是,如果當前要代理的類對象沒有實現某個接口的話,則使用this;target指示符用於基於JDK動態代理的代理類,通俗的來講就是如果當前要代理的目標對象有實現了某個接口的話,則使用target.:
public class FooDao implements BarDao {

}
比如在上面這段代碼示例中,spring aop將使用jdk的動態代理來實現切面編程,在編寫匹配這類型的目標對象的連接點表達式時要使用target指示符, 如下所示:

@Pointcut(“target(org.baeldung.dao.BarDao)”)
如果FooDao類沒有實現任何接口,或者在spring aop配置屬性:proxyTargetClass設爲true時,Spring Aop會使用基於CGLIB的動態字節碼技爲目標對象生成一個子類將爲代理類,這時應該使用this指示器:

@Pointcut(“this(org.baeldung.dao.FooDao)”)

參數
參數指示符是一對括號所括的內容,用來匹配指定方法參數:

@Pointcut(“execution(* *…find*(Long))”)
這個切點匹配所有以find開頭的方法,並且只一個Long類的參數。如果我們想要匹配一個有任意個參數,但是第一個參數必須是Long類的,我們這可使用下面這個切點表達式:
@Pointcut(“execution(* *…find*(Long,…))”)

@Target
這個指示器匹配指定連接點,這個連接點所屬的目標對象的類有一個指定的註解:

@Pointcut("@target(org.springframework.stereotype.Repository)")

@args
這個指示符是用來匹配連接點的參數的,@args指出連接點在運行時傳過來的參數的類必須要有指定的註解,假設我們希望切入所有在運行時接受實@Entity註解的bean對象的方法:

@Pointcut("@args(org.baeldung.aop.annotations.Entity)")
public void methodsAcceptingEntities() {}

爲了在切面裏接收並使用這個被@Entity的對象,我們需要提供一個參數給切面通知:JointPoint:

@Before(“methodsAcceptingEntities()”)
public void logMethodAcceptionEntityAnnotatedBean(JoinPoint jp) {
logger.info("Accepting beans with @Entity annotation: " + jp.getArgs()[0]);
}

@within
這個指示器,指定匹配必須包括某個註解的的類裏的所有連接點:

@Pointcut("@within(org.springframework.stereotype.Repository)")
上面的切點跟以下這個切點是等效的:

@Pointcut(“within(@org.springframework.stereotype.Repository *)”)

@annotation
這個指示器匹配那些有指定註解的連接點,比如,我們可以新建一個這樣的註解@Loggable:

@Pointcut("@annotation(org.baeldung.aop.annotations.Loggable)")
public void loggableMethods() {}
我們可以使用@Loggable註解標記哪些方法執行需要輸出日誌:
@Before(“loggableMethods()”)
public void logMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("Executing method: " + methodName);
}

切點表達式組合
可以使用&&、||、!、三種運算符來組合切點表達式,表示與或非的關係。

@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}

@Pointcut(“execution(* *…create*(Long,…))”)
public void firstLongParamMethods() {}

@Pointcut(“repositoryMethods() && firstLongParamMethods()”)
public void entityCreationMethods() {}

總結

上述描述的緩存實現通過AOP方式實現了對代碼的低侵入性,使用常用的nosql數據庫redis做緩存數據庫,使用jedis調用redis API進行數據操作。

好了,通過本博文你應該是瞭解瞭如何通過AOP方式使用redis進行緩存操作了,如果本博文對您有些許幫助,還請您“點贊”和“評論”支持一下,謝謝~

推薦閱讀:
Git-【技術乾貨】工作中Git的使用實踐
shell-【技術乾貨】編寫shell腳本所需的語法和示例
Git - 使用git不知道內部實現機制怎麼行

參考blog:博客 博客

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章