前言
近期有同學(xué)問我,怎么在不使用spring自帶的@Cacheable
來 使用aop方式用redis為項目接口調(diào)用添加緩存,在這里總結(jié)整理一下,博文難免會有紕漏,如有問題請評論不吝告知。
在本文章,你會了解到如何使用redis,以及如何通過jedis操作redis通過AOP的方式實現(xiàn)緩存。在文章后面還介紹了AOP的相關(guān)知識點,希望對大家有些許幫助~
如果轉(zhuǎn)載此博文,請附上本文鏈接,謝謝合作~
如果感覺這篇文章對您有所幫助,請“點贊”或者“關(guān)注”博主,您的喜歡和關(guān)注將是我前進(jìn)的最大動力~
一:環(huán)境準(zhǔn)備
1:準(zhǔn)備Redis環(huán)境
使用redis做緩存的話,需要有redis服務(wù),可以將服務(wù)部署在遠(yuǎn)程服務(wù)器上,也可以部署到本機上。
1.1. 部署在linux服務(wù)器
1.1.1安裝Redis
#安裝redis,當(dāng)前最新的版本是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服務(wù)并使用
#啟動redis服務(wù)
$ cd src
$ ./redis-server
#使用redis客戶端測試redis
$ cd src
$ ./redis-cli
redis> set testkey testvalue
OK
redis> get testkey
"testvalue"
如果上述過程沒有報錯的話,那么恭喜你啟動redis服務(wù)成功,下面我們將會使用jedis操作redis來實現(xiàn)緩存
1.2. 部署在windows服務(wù)器
2.1下載redis壓縮包
下載zip壓縮包(Redis-x64-*.zip):https://github.com/MSOpenTech/redis/releases
將其解壓到某一文件夾中,重命名為Redis
2.2啟動redis服務(wù)并使用
打開cmd,切換到解壓的Redis文件夾中,運行如下命令,
會發(fā)現(xiàn)出現(xiàn)”The server is now ready to accept connections on port 6379“字樣表示啟動成功
redis-server.exe redis.windows.conf
再打開一個cmd,
原來的cmd不要關(guān)閉,保持打開狀態(tài)
,輸入以下命令:
其中:127.0.0.1:為你的redis服務(wù)ip地址,如果是本機安裝的就是127.0.0.1
,端口6379是redis默認(rèn)監(jiān)聽的端口
redis-cli.exe -h 127.0.0.1 -p 6379
#如果redis設(shè)置了密碼,可以添加參數(shù)-a指定密碼,例如:
redis-cli.exe -h 127.0.0.1 -p 6379 -a 12345
可以使用redis命令測試是否可以正常使用,至此redis服務(wù)便準(zhǔn)備完畢了~
2:準(zhǔn)備項目環(huán)境
- 首先spring boot項目,當(dāng)然不是boot項目也可以,我是以boot項目舉例的
- pom文件添加依賴,只列出了此功能設(shè)計特殊所需的
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>
- application.yml添加配置,如果你是xml格式的文件,yml格式和xml格式類似,只是yml格式更加明了一些,google一下轉(zhuǎn)換一下格式就行
spring:
jedis:
max:
total: 100 #jedis總量
idle: 50 #空閑jedis實例最大值
waitmillis: 500 #當(dāng)池內(nèi)沒有返回jedis對象時,最大等待時間
timout: 0 #當(dāng)客戶端閑置多長時間后關(guān)閉連接,如果指定為 0,表示關(guān)閉該功能,連接不會斷
host: 127.0.0.1 # redis服務(wù)ip地址
port: 6379 # 端口
password: test # redis密碼
ps : redis的常用配置參數(shù)
此處的參數(shù)需要根據(jù)想要緩存接口的調(diào)用情況進(jìn)行動態(tài)配置。
至此,環(huán)境配置完成了,現(xiàn)在只需要操作redis實現(xiàn)緩存了~~
二:緩存功能實現(xiàn)
1:過程簡介
- 對于不加緩存的項目,我們每一次的請求都會去數(shù)據(jù)庫中查詢,即使兩次請求一樣并且獲取的數(shù)據(jù)一樣,也是會去查詢數(shù)據(jù)庫,這就造成了數(shù)據(jù)庫資源的浪費,并且如果并發(fā)量特別高的話,數(shù)據(jù)庫的壓力太大,容易造成查詢緩慢、數(shù)據(jù)庫宕機、查詢失敗等問題。
- 項目添加緩存之后,請求查詢數(shù)據(jù)的時候會先查詢緩存,緩存(這里指只有一級緩存)中沒有才會到達(dá)數(shù)據(jù)庫。相同的請求在緩存還沒有過期 的情況下,會得到緩存中的數(shù)據(jù)并返回,不會到達(dá)數(shù)據(jù)庫,這樣做即減少了數(shù)據(jù)庫的壓力提高了并發(fā)量又提升了查詢速度。
- 簡易流程圖:
graph LR
A[請求]-->B[查詢緩存]
B -- 數(shù)據(jù)不在緩存中 --> C[查詢數(shù)據(jù)庫]
E--> D[返回數(shù)據(jù)]
B -- 數(shù)據(jù)在緩存中 --> E[獲得緩存中數(shù)據(jù)]
C --> F[將獲得數(shù)據(jù)緩存到緩存中]
F -->D
2:緩存AOP實現(xiàn)
在使用aop之前,先大致的了解一下 aop:
AOP(Aspect Oriented Programing):面向切面編程,將通用的邏輯從業(yè)務(wù)邏輯中分離出來。
AOP把軟件系統(tǒng)分為兩個部分:核心關(guān)注點和橫切關(guān)注點:
- 主要的業(yè)務(wù)處理部分。業(yè)務(wù)處理的主要流程是核心關(guān)注點,與之關(guān)系不大的部分是橫切關(guān)注點。
- 橫切關(guān)注點的一個特點是,他們經(jīng)常發(fā)生在核心關(guān)注點的多處,而各處都基本相似。比如權(quán)限認(rèn)證、日志、事務(wù)處理。Aop 的作用在于分離系統(tǒng)中的各種關(guān)注點,將核心關(guān)注點和橫切關(guān)注點分離開來。
正如Avanade公司的高級方案構(gòu)架師Adam Magee所說,AOP的核心思想就是“將應(yīng)用程序中的商業(yè)邏輯同對其提供支持的通用服務(wù)進(jìn)行分離”。
AOP的底層實現(xiàn)主要是依賴動態(tài)代理來實現(xiàn)的:
- 比如Spring aop底層使用JDK proxy(實現(xiàn)接口)和CGLib(繼承目標(biāo)類、使用ASM庫)來生成代理類來代替目標(biāo)類執(zhí)行,默認(rèn)使用JDK proxy ,當(dāng)無接口時使用CGLib。底層采用動態(tài)代理技術(shù)(動態(tài)代理技術(shù)底層依賴的反射技術(shù))在運行期動態(tài)生成代理類(不同于aspectJ編譯期織入代碼到目標(biāo)類)。
- 再比如AspectJ做為java實現(xiàn)的統(tǒng)一AOP解決方案,使用ASM字節(jié)碼操作庫,需要使用特定的acj編譯器(不同于spring使用動態(tài)代理)在編譯期將代碼直接織入到目標(biāo)類。
下面會詳細(xì)介紹aop相關(guān)
2.1.執(zhí)行過程
- 請求到達(dá)
Controller
中的接口時,因為我們在CacheAspect
類中配置的切入點包含這個接口,所以進(jìn)入CacheAspect
類的doAround
方法中執(zhí)行緩存操作 - 在
doAround
中,首先獲取key,判斷redis中是否包含key,包含就返回緩存中的數(shù)據(jù),完成請求 - 不包含就執(zhí)行調(diào)用的接口通過查詢數(shù)據(jù)庫獲取數(shù)據(jù),并將其緩存到redis中,完成一次請求不包含就執(zhí)行調(diào)用的接口通過查詢數(shù)據(jù)庫獲取數(shù)據(jù),并將其緩存到redis中,完成請求
2.2. 組成部分與實現(xiàn)
- 自定義注解:
NeedCacheAop
用在方法上面標(biāo)識調(diào)用該方法的請求需要被緩存
其中的nxxx、expx、time等參數(shù)是為了可以更靈活的空值緩存的方式與過期時間,具體含義請看下面”其他“中的set方法參數(shù)解析
/**
* 自定義注解,用于標(biāo)識方法是否需要使用緩存
*/
@Target({ElementType.PARAMETER, ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedCacheAop {
//代表緩存策咯,nx:代表key不存在再進(jìn)行緩存kv,xx:代表key存在再進(jìn)行緩存kv 默認(rèn)為"不存在key緩存key"
String nxxx() default "nx";
//代表過期時間單位,ex:秒 px:毫秒 默認(rèn)為"秒"
String expx() default "ex";
//過期時間
long time() default 30*60;
}
- 序列化工具類:
SerializeUtil
使用FastJso對要緩存的數(shù)據(jù)進(jìn)行序列化后存儲與獲取緩存中的反序列化
使用fastjson對數(shù)據(jù)進(jìn)行序列化與反序列化,非常簡單
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
接口 與其實現(xiàn)類CacheServiceImpl
方法內(nèi)部封裝了關(guān)于緩存的get set containKey getKeyAop等方法
public interface CacheService {
/**獲取jedis實例*/
Jedis getResource() throws Exception;
/**設(shè)置key與value*/
void set(String key, String value, String nxxx, String expx, long time);
/**根據(jù)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();
}
/**設(shè)置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);
}
}
/**根據(jù)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){
//獲取參數(shù)的序列化
Object[] objects = joinPoint.getArgs();
String args = SerializeUtil.serializeObject(objects[0]);
//獲取請求url
String url = request.getRequestURI();
//獲取請求的方法
String method = request.getMethod();
//獲取當(dāng)前日期,規(guī)避默認(rèn)init情況
String date = LocalDate.now().toString();
//key值獲取
return args + url + method + date;
}
}
- 切面類:
CacheAspect
用于對相應(yīng)的請求接口切入緩存存取的相關(guān)邏輯,使用AOP可以對代碼0侵入性,是一個很好的方法
@Component
@Aspect
public class CacheAspect {
@Autowired
CacheService cacheService;
/**設(shè)置切入點*/
//方法上面有@NeedCacheAop的方法,增加靈活性
@Pointcut("@annotation(com.xcar.data.web.backend.util.annotation.NeedCacheAop)")
public void annotationAspect(){}
//相應(yīng)包下所有以XcarIndex開頭的類中的所有方法,減少代碼侵入性
@Pointcut("execution(public * com.xcar.data.web.backend.controller.XcarIndex*.*(..))")
public void controllerAspect(){}
/**環(huán)繞通知*/
@Around(value = "controllerAspect()||annotationAspect()")
public Object doAround(ProceedingJoinPoint joinPoint){
//獲取請求
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
//存儲接口返回值
Object object = new Object();
//獲取注解對應(yīng)配置過期時間
NeedCacheAop cacheAop = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(NeedCacheAop.class); //獲取注解自身
String nxxx;String expx;long time;
if (cacheAop == null){//規(guī)避使用第二種切點進(jìn)行緩存操作的情況
nxxx = "nx";
expx = "ex";
time = 30*60; //默認(rèn)過期時間為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)){ //規(guī)避redis服務(wù)不可用
try {
//執(zhí)行接口調(diào)用的方法
joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}else{
JSONObject klass = SerializeUtil.unserializeObject(obj);
return new ResponseEntity<>(klass.get("body"), HttpStatus.OK) ;
}
}else{
try {
////執(zhí)行接口調(diào)用的方法并獲取返回值
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的相關(guān)參數(shù),與創(chuàng)建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() {}
/**設(shè)置*/
@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方法省略
}
- 響應(yīng)數(shù)據(jù)對象:
XcarIndexCarAttentionIndexResponse
響應(yīng)的數(shù)據(jù)對象,緩存就是對其進(jìn)行序列化后緩存
該對象類一定繼承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接口的實現(xiàn),例如:我要切入的接口
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實現(xiàn)
在一些情況下,我們需要對方法內(nèi)部一些中間查詢結(jié)果進(jìn)行緩存,這樣就只能將緩存數(shù)據(jù)的代碼直接寫在方法體內(nèi),實現(xiàn)也相對AOP實現(xiàn)方式來說更加簡單,調(diào)用相關(guān)的jedis方法即可,可參考上述代碼實現(xiàn)。
三:知識點
1:jedis中set方法參數(shù):
- key :緩存的key值
- value :緩存的value值
- nxxx: NX|XX兩種選擇, NX -- 緩存不存在時才進(jìn)行緩存. XX -- 緩存存在時再進(jìn)行緩存
- expx :EX|PX兩種選擇, 過期時間的代為,EX 代表秒; PX 代表毫秒
- time :過期時間的數(shù)值
2:AOP面向切面編程
如上述所說:
AOP(Aspect Oriented Programing):面向切面編程,將通用的邏輯從業(yè)務(wù)邏輯中分離出來。AOP把軟件系統(tǒng)分為兩個部分:核心關(guān)注點和橫切關(guān)注點。業(yè)務(wù)處理的主要流程是核心關(guān)注點,與之關(guān)系不大的部分是橫切關(guān)注點。橫切關(guān)注點的一個特點是,他們經(jīng)常發(fā)生在核心關(guān)注點的多處,而各處都基本相似。比如權(quán)限認(rèn)證、日志、事務(wù)處理。Aop 的作用在于分離系統(tǒng)中的各種關(guān)注點,將核心關(guān)注點和橫切關(guān)注點分離開來。
正如Avanade公司的高級方案構(gòu)架師Adam Magee所說,AOP的核心思想就是“將應(yīng)用程序中的商業(yè)邏輯同對其提供支持的通用服務(wù)進(jìn)行分離”。
AOP的底層實現(xiàn)主要是依賴動態(tài)代理來實現(xiàn)的:
- 比如Spring aop底層使用JDK proxy(實現(xiàn)接口)和CGLib(繼承目標(biāo)類、使用ASM庫)來生成代理類來代替目標(biāo)類執(zhí)行,默認(rèn)使用JDK proxy ,當(dāng)無接口時使用CGLib。底層采用動態(tài)代理技術(shù)(動態(tài)代理技術(shù)底層依賴的反射技術(shù))在運行期動態(tài)生成代理類(不同于aspectJ編譯期織入代碼到目標(biāo)類)。
- 再比如AspectJ做為java實現(xiàn)的統(tǒng)一AOP解決方案,使用ASM字節(jié)碼操作庫,需要使用特定的acj編譯器(不同于spring使用動態(tài)代理)在編譯期將代碼直接織入到目標(biāo)類。
AOP相關(guān)概念:
- 連接點(Joinpoint): 表示需要在程序中插入橫切關(guān)注點的擴(kuò)展點,連接點可能是類初始化、方法執(zhí)行、方法調(diào)用、字段調(diào)用或處理異常等等,Spring只支持方法執(zhí)行連接點;在AOP中表示為“在哪里干”;
- 切入點(Pointcut): 選擇一組相關(guān)連接點的模式,即可以認(rèn)為連接點的集合,Spring支持perl5正則表達(dá)式和AspectJ切入點模式,Spring默認(rèn)使用AspectJ語法;在AOP中表示為“在哪里干的集合”;
- 通知(Advice): 在連接點上執(zhí)行的行為,通知提供了在AOP中需要在切入點所選擇的連接點處進(jìn)行擴(kuò)展現(xiàn)有行為的手段;包括前置通知(before advice)、后置通知(after advice)、環(huán)繞通知(around advice),在Spring中通過代理模式實現(xiàn)AOP,并通過攔截器模式以環(huán)繞連接點的攔截器鏈織入通知;在AOP中表示為“干什么”;
- 切面(Aspect):橫切關(guān)注點的模塊化,比如日志組件。可以認(rèn)為是通知、引入和切入點的組合;在Spring中可以使用Schema和@AspectJ方式進(jìn)行組織實現(xiàn);在AOP中表示為“在哪干和干什么集合”;
- 引入(Introduction): 也稱為內(nèi)部類型聲明,為已有的類添加額外新的字段或方法,Spring允許引入新的接口(必須對應(yīng)一個實現(xiàn))到所有被代理對象(目標(biāo)對象);在AOP中表示為“干什么(引入什么)”;
- 目標(biāo)對象(Target Object):需要被織入橫切關(guān)注點的對象,即該對象是切入點選擇的對象,需要被通知的對象,從而也可稱為“被通知對象”;由于Spring AOP 通過代理模式實現(xiàn),從而這個對象永遠(yuǎn)是被代理對象;在AOP中表示為“對誰干”;
- AOP代理(AOP Proxy): AOP框架使用代理模式創(chuàng)建的對象,從而實現(xiàn)在連接點處插入通知(即應(yīng)用切面),就是通過代理來對目標(biāo)對象應(yīng)用切面。在Spring中,AOP代理可以用JDK動態(tài)代理或CGLIB代理實現(xiàn),而通過攔截器模型應(yīng)用切面。
- 織入(Weaving): 織入是一個過程,是將切面應(yīng)用到目標(biāo)對象從而創(chuàng)建出AOP代理對象的過程,織入可以在編譯期、類裝載期、運行期進(jìn)行。組裝方面來創(chuàng)建一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運行時完成(jdk自帶的動態(tài)代理)。Spring和其他純Java AOP框架一樣,在運行時完成織入。
3:AOP中切點表達(dá)式
這部分內(nèi)容來自該 博客
切點指示符
切點指示符是切點定義的關(guān)鍵字,切點表達(dá)式以切點指示符開始。開發(fā)人員使切點指示符來告訴切點將要匹配什么,有以下9種切點指示符:execution、within、this、target、args、@target、@args、@within、@annotation,下面一一介結(jié)這9種切點指示符。
execution
execution是一種使用頻率比較高比較主要的一種切點指示符,用來匹配方法簽名,方法簽名使用全限定名,包括訪問修飾符(public/private/protected)、返回類型,包名、類名、方法名、參數(shù),其中返回類型,包名,類名,方法,參數(shù)是必須的,如下面代碼片段所示:
@Pointcut("execution(public String org.baeldung.dao.FooDao.findById(Long))")
上面的代碼片段里的表達(dá)式精確地匹配到FooDao類里的findById(Long)方法,但是這看起來不是很靈活。假設(shè)我們要匹配FooDao類的所有方法,這些方法可能會有不同的方法名,不同的返回值,不同的參數(shù)列表,為了達(dá)到這種效果,我們可以使用通配符。如下代碼片段所示:
@Pointcut("execution(* org.baeldung.dao.FooDao.*(..))")
第一個通配符匹配所有返回值類型,第二個匹配這個類里的所有方法,()括號表示參數(shù)列表,括號里的用兩個點號表示匹配任意個參數(shù),包括0個
within
使用within切點批示符可以達(dá)到上面例子一樣的效果,within用來限定連接點屬于某個確定類型的類。如下面代碼的效果與上面的例子是一樣的:
@Pointcut("within(org.baeldung.dao.FooDao)")
我們也可以使用within指示符來匹配某個包下面所有類的方法(包括子包下面的所有類方法),如下代碼所示:
@Pointcut("within(org.baeldung..*)")
this 和 target
this用來匹配的連接點所屬的對象引用是某個特定類型的實例,target用來匹配的連接點所屬目標(biāo)對象必須是指定類型的實例;那么這兩個有什么區(qū)別呢?原來AspectJ在實現(xiàn)代理時有兩種方式:
1、如果當(dāng)前對象引用的類型沒有實現(xiàn)自接口時,spring aop使用生成一個基于CGLIB的代理類實現(xiàn)切面編程
2、如果當(dāng)前對象引用實現(xiàn)了某個接口時,Spring aop使用JDK的動態(tài)代理機制來實現(xiàn)切面編程
this指示符就是用來匹配基于CGLIB的代理類,通俗的來講就是,如果當(dāng)前要代理的類對象沒有實現(xiàn)某個接口的話,則使用this;target指示符用于基于JDK動態(tài)代理的代理類,通俗的來講就是如果當(dāng)前要代理的目標(biāo)對象有實現(xiàn)了某個接口的話,則使用target.:
public class FooDao implements BarDao {
...
}
比如在上面這段代碼示例中,spring aop將使用jdk的動態(tài)代理來實現(xiàn)切面編程,在編寫匹配這類型的目標(biāo)對象的連接點表達(dá)式時要使用target指示符, 如下所示:
@Pointcut("target(org.baeldung.dao.BarDao)")
如果FooDao類沒有實現(xiàn)任何接口,或者在spring aop配置屬性:proxyTargetClass設(shè)為true時,Spring Aop會使用基于CGLIB的動態(tài)字節(jié)碼技為目標(biāo)對象生成一個子類將為代理類,這時應(yīng)該使用this指示器:
@Pointcut("this(org.baeldung.dao.FooDao)")
參數(shù)
參數(shù)指示符是一對括號所括的內(nèi)容,用來匹配指定方法參數(shù):
@Pointcut("execution(* *..find(Long))")
這個切點匹配所有以find開頭的方法,并且只一個Long類的參數(shù)。如果我們想要匹配一個有任意個參數(shù),但是第一個參數(shù)必須是Long類的,我們這可使用下面這個切點表達(dá)式:
@Pointcut("execution( *..find*(Long,..))")
@Target
這個指示器匹配指定連接點,這個連接點所屬的目標(biāo)對象的類有一個指定的注解:
@Pointcut("@target(org.springframework.stereotype.Repository)")
@args
這個指示符是用來匹配連接點的參數(shù)的,@args指出連接點在運行時傳過來的參數(shù)的類必須要有指定的注解,假設(shè)我們希望切入所有在運行時接受實@Entity注解的bean對象的方法:
@Pointcut("@args(org.baeldung.aop.annotations.Entity)")
public void methodsAcceptingEntities() {}
為了在切面里接收并使用這個被@Entity的對象,我們需要提供一個參數(shù)給切面通知: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注解標(biāo)記哪些方法執(zhí)行需要輸出日志:
@Before("loggableMethods()")
public void logMethod(JoinPoint jp) {
String methodName = jp.getSignature().getName();
logger.info("Executing method: " + methodName);
}
切點表達(dá)式組合
可以使用&&、||、!、三種運算符來組合切點表達(dá)式,表示與或非的關(guān)系。
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryMethods() {}
@Pointcut("execution(* *..create*(Long,..))")
public void firstLongParamMethods() {}
@Pointcut("repositoryMethods() && firstLongParamMethods()")
public void entityCreationMethods() {}
總結(jié)
上述描述的緩存實現(xiàn)通過AOP方式實現(xiàn)了對代碼的低侵入性,使用常用的nosql數(shù)據(jù)庫redis做緩存數(shù)據(jù)庫,使用jedis調(diào)用redis API進(jìn)行數(shù)據(jù)操作。
好了,通過本博文你應(yīng)該是了解了如何通過AOP方式使用redis進(jìn)行緩存操作了,如果本博文對您有些許幫助,還請您“點贊”和“評論”支持一下,謝謝~
推薦閱讀:
Git-【技術(shù)干貨】工作中Git的使用實踐
shell-【技術(shù)干貨】編寫shell腳本所需的語法和示例
Git - 使用git不知道內(nèi)部實現(xiàn)機制怎么行