摘要
- 通過(guò)本文垫言,你將知道如何在WebFlux項(xiàng)目中通過(guò)redis注解緩存方法的返回值
- 本項(xiàng)目基于springboot:2.4.0造挽,jdk1.8,并使用Maven構(gòu)建
- 代碼地址:https://github.com/hanqunfeng/reactive-redis-cache-annotation-spring-boot-starter
前言
最近在使用WebFlux時(shí)發(fā)現(xiàn),SpringBoot提供的@Cacheable,@CachePut熙含,@CacheEvict和@Caching注解不支持響應(yīng)式方法,SpringBoot官方也沒(méi)有提供響應(yīng)式方法的緩存注解艇纺,看到網(wǎng)上的一些解決方案都是直接在方法代碼中加入緩存數(shù)據(jù)的代碼邏輯怎静,這樣雖然可以解決問(wèn)題,但是代碼侵入并不優(yōu)雅黔衡,于是萌生自己寫(xiě)一個(gè)基于redis的響應(yīng)式方法緩存注解的想法蚓聘,本項(xiàng)目參考SpringBoot提供的@Cacheable,@CachePut盟劫,@CacheEvict和@Caching注解聲明夜牡,但是只是實(shí)現(xiàn)了一些基本功能,可以滿足絕大部分使用場(chǎng)景的要求侣签,因?yàn)镾pringBoot早晚會(huì)給出官方解決方案塘装,在此之前,不妨一試影所。
使用示例
- 本項(xiàng)目已經(jīng)發(fā)布到maven中央倉(cāng)庫(kù)蹦肴,直接在項(xiàng)目中添加依賴即可。
- 本項(xiàng)目雖然基于springboot:2.4.0構(gòu)建猴娩,但實(shí)際上springboot2.0+都可以使用阴幌。
- maven依賴
<dependency>
<groupId>com.hanqunfeng</groupId>
<artifactId>reactive-redis-cache-annotation-spring-boot-starter</artifactId>
<version>1.1.0</version>
</dependency>
- gradle依賴
implementation 'com.hanqunfeng:reactive-redis-cache-annotation-spring-boot-starter:1.1.0'
- 此時(shí)項(xiàng)目中可能還要添加其它依賴,以gradle舉例
//webflux胀溺,非必須裂七,主要是面向響應(yīng)式編程的,所以使用springboot大概率會(huì)使用webflux
implementation 'org.springframework.boot:spring-boot-starter-webflux'
//Spring Boot Redis 依賴仓坞,或者spring-boot-starter-data-redis-reactive背零,任選其一即可,注意要在配置文件中加入redis的配置
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
//redis lettuce連接池依賴无埃,也可以使用jedis連接池徙瓶,非必須毛雇,正式環(huán)境建議開(kāi)啟連接池
implementation 'org.apache.commons:commons-pool2'
//aop 面向方面編程 支持@Aspect,非必須
implementation 'org.springframework.boot:spring-boot-starter-aop'
- 方法返回值必須是Mono或者Flux類型侦镇,使用方式與springboot提供的Cacheable等注解類似
/**
* 緩存 cacheName和key支持EL表達(dá)式灵疮,實(shí)際key的名稱是"cacheName_key"
* 緩存結(jié)果
* key:sysuser_find_lisi
* value:
* [
* "com.example.model.SysUser"
* {
* id:"5c74a4e4-c4f2-4570-8735-761d7a570d36"
* username:"lisi"
* password:"$2a$10$PXoGXLwg05.5YO.QtZa46ONypBmiK59yfefvO1OGO8kYFwzOB.Os6"
* enable:true
* }
* ]
*/
@ReactiveRedisCacheable(cacheName = "sysuser", key = "'find_' + #username")
public Mono<SysUser> findUserByUsername(String username) {
return sysUserRepository.findByUsername(username);
}
@ReactiveRedisCacheable(cacheName = "sysuser", key = "all")
public Flux<SysUser> findAll() {
return sysUserRepository.findAll();
}
/**
* 刪除緩存,allEntries = true 表示刪除全部以"cacheName_"開(kāi)頭的緩存
* allEntries 默認(rèn)false壳繁,此時(shí)需要指定key的值震捣,表示刪除指定的"cacheName_key"
*/
@ReactiveRedisCacheEvict(cacheName = "sysuser", allEntries = true)
public Mono<SysUser> add(SysUser sysUser) {
return sysUserRepository.addSysUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), sysUser.getEnable()).flatMap(data -> sysUserRepository.findById(sysUser.getId()));
}
/**
* 組合注解,用法與@Caching類似
* 規(guī)則:
* 1.cacheables不能與cacheEvicts或者cachePuts同時(shí)存在闹炉,因?yàn)楹笳咭欢〞?huì)執(zhí)行方法主體蒿赢,達(dá)不到調(diào)用緩存的目的,所以當(dāng)cacheables存在時(shí)渣触,后者即便指定也不執(zhí)行
* 2.先執(zhí)行cacheEvicts羡棵,再執(zhí)行cachePuts
*/
@ReactiveRedisCaching(
evict = {@ReactiveRedisCacheEvict(cacheName = "sysuser", key = "all")},
put = {@ReactiveRedisCachePut(cacheName = "sysuser", key = "'find_' + #sysUser.username")}
)
public Mono<SysUser> update(SysUser sysUser) {
Mono<SysUser> save = sysUserRepository.save(sysUser);
return save;
}
/**
* 刪除指定的"cacheName_key"
*/
@ReactiveRedisCacheEvict(cacheName = "sysuser", key="'find_' + #username")
public Mono<Boolean> deleteByUserName(String username) {
return sysUserRepository.deleteByUsername(username);
}
RedisTemplate
- 如果使用時(shí)沒(méi)有創(chuàng)建RedisTemplate,本項(xiàng)目中提供了一個(gè)默認(rèn)的RedisTemplate嗅钻,基于jackson序列化皂冰,支持jdk8的LocalDate和LocalDateTime
@Bean
@ConditionalOnMissingBean(value = RedisTemplate.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.debug("ReactiveRedisConfig RedisTemplate");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
//LocalDateTime系列序列化和反序列化模塊,繼承自jsr310养篓,我們?cè)谶@里修改了日期格式
JavaTimeModule javaTimeModule = new JavaTimeModule();
//序列化
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
javaTimeModule.addSerializer(LocalTime.class,
new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//反序列化
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
javaTimeModule.addDeserializer(LocalTime.class,
new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注冊(cè)模塊
objectMapper.registerModule(javaTimeModule);
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
serializer.setObjectMapper(objectMapper);
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
以下為源碼說(shuō)明
源碼依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
自定義Redis緩存相關(guān)注解
- 只支持方法返回類型為Mono或者Flux
- 其它返回類型時(shí)請(qǐng)使用springboot提供的Cacheable秃流,CachePut,CacheEvict和Caching
- 使用方式與springboot提供的Cacheable觉至,CachePut剔应,CacheEvict和Caching類似,具體看本文上面的示例
ReactiveRedisCacheable
package com.hanqunfeng.reactive.redis.cache.aop;
import java.lang.annotation.*;
/**
* <h1>redis方法緩存注解</h1>
* Created by hanqf on 2020/11/21 18:28.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReactiveRedisCacheable {
/**
* 緩存key语御,key為cacheName+"_"+key
* 支持EL表達(dá)式
*/
String key();
/**
* 緩存key分組峻贮,會(huì)做為緩存key的前綴+"_"
* 支持EL表達(dá)式
*/
String cacheName();
/**
* 緩存過(guò)期時(shí)間,單位秒应闯,默認(rèn)24小時(shí)
*/
long timeout() default 24 * 3600L;
}
ReactiveRedisCacheEvict
package com.hanqunfeng.reactive.redis.cache.aop;
import java.lang.annotation.*;
/**
* <h1>redis清除緩存注解</h1>
* Created by hanqf on 2020/11/21 22:26.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReactiveRedisCacheEvict {
/**
* 緩存key纤控,如果cacheName不為空,則key為cacheName+"_"+key
* 支持EL表達(dá)式
*/
String key() default "";
/**
* 緩存key分組碉纺,會(huì)做為緩存key的前綴+"_"
* 支持EL表達(dá)式
*/
String cacheName();
/**
* 是否刪除cacheName下全部緩存數(shù)據(jù)船万,
* true時(shí)cacheName不能為空,此時(shí)即便指定了key值骨田,也會(huì)刪除cacheName下全部緩存
* false時(shí)key值不能為空
*/
boolean allEntries() default false;
/**
* 調(diào)用清除緩存的時(shí)機(jī)耿导,true:執(zhí)行方法前,false:執(zhí)行方法后
* 如果是false态贤,則方法執(zhí)行過(guò)程中發(fā)生異常舱呻,則不會(huì)清除緩存
*/
boolean beforeInvocation() default false;
}
ReactiveRedisCachePut
package com.hanqunfeng.reactive.redis.cache.aop;
import java.lang.annotation.*;
/**
* <h1>執(zhí)行完方法更新緩存</h1>
* Created by hanqf on 2020/11/21 23:15.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReactiveRedisCachePut {
/**
* 緩存key,key為cacheName+"_"+key
* 支持EL表達(dá)式
*/
String key();
/**
* 緩存key分組,會(huì)做為緩存key的前綴+"_"
* 支持EL表達(dá)式
*/
String cacheName();
/**
* 緩存過(guò)期時(shí)間箱吕,單位秒芥驳,默認(rèn)24小時(shí)
*/
long timeout() default 24 * 3600L;
}
ReactiveRedisCaching
package com.hanqunfeng.reactive.redis.cache.aop;
import java.lang.annotation.*;
/**
* <h1>組合</h1>
* Created by hanqf on 2020/11/21 23:24.
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReactiveRedisCaching {
ReactiveRedisCacheable[] cacheable() default {};
ReactiveRedisCachePut[] put() default {};
ReactiveRedisCacheEvict[] evict() default {};
}
AOP--ReactiveRedisCacheAspect
- 支持方法返回類型為Mono或者Flux
package com.hanqunfeng.reactive.redis.cache.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* <h1>redis緩存aop</h1>
* Created by hanqf on 2020/11/21 16:16.
*/
@Component
//標(biāo)識(shí)是一個(gè)Aspect代理類
@Aspect
//如果有多個(gè)切面攔截相同的切點(diǎn),可以用@Order指定執(zhí)行順序
//@Order(1)
@Slf4j
public class ReactiveRedisCacheAspect {
@Autowired
private RedisTemplate redisTemplate;
@Pointcut("@annotation(com.hanqunfeng.reactive.redis.cache.aop.ReactiveRedisCacheable)")
public void cacheablePointCut() {
}
@Pointcut("@annotation(com.hanqunfeng.reactive.redis.cache.aop.ReactiveRedisCacheEvict)")
public void cacheEvictPointCut() {
}
@Pointcut("@annotation(com.hanqunfeng.reactive.redis.cache.aop.ReactiveRedisCachePut)")
public void cachePutPointCut() {
}
@Pointcut("@annotation(com.hanqunfeng.reactive.redis.cache.aop.ReactiveRedisCaching)")
public void cachingPointCut() {
}
//環(huán)繞通知,一般不建議使用茬高,可以通過(guò)@Before和@AfterReturning實(shí)現(xiàn)
//但是響應(yīng)式方法只能通過(guò)環(huán)繞通知實(shí)現(xiàn)aop兆旬,因?yàn)槠渌ㄖ獣?huì)導(dǎo)致不再同一個(gè)線程執(zhí)行
@Around("cacheablePointCut()")
public Object cacheableAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.debug("ReactiveRedisCacheAspect cacheableAround....");
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
String returnTypeName = method.getReturnType().getSimpleName();
ReactiveRedisCacheable annotation = method.getAnnotation(ReactiveRedisCacheable.class);
String cacheName = annotation.cacheName();
String key = annotation.key();
long timeout = annotation.timeout();
//轉(zhuǎn)換EL表達(dá)式
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
String redis_key = cacheName + "_" + key;
boolean hasKey = redisTemplate.hasKey(redis_key);
if (hasKey) {
Object o = redisTemplate.opsForValue().get(redis_key);
if (returnTypeName.equals("Flux")) {
return Flux.fromIterable((List) o);
} else if (returnTypeName.equals("Mono")) {
return Mono.just(o);
} else {
return o;
}
} else {
//實(shí)際執(zhí)行的方法
Object proceed = proceedingJoinPoint.proceed();
if (returnTypeName.equals("Flux")) {
return ((Flux) proceed).collectList().doOnNext(list -> redisTemplate.opsForValue().set(redis_key, list, timeout, TimeUnit.SECONDS)).flatMapMany(list -> Flux.fromIterable((List) list));
} else if (returnTypeName.equals("Mono")) {
return ((Mono) proceed).doOnNext(obj -> redisTemplate.opsForValue().set(redis_key, obj, timeout, TimeUnit.SECONDS));
} else {
return proceed;
}
}
}
@Around("cacheEvictPointCut()")
public Object cacheEvictAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.debug("ReactiveRedisCacheAspect cacheEvictAround....");
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
String returnTypeName = method.getReturnType().getSimpleName();
ReactiveRedisCacheEvict annotation = method.getAnnotation(ReactiveRedisCacheEvict.class);
String cacheName = annotation.cacheName();
String key = annotation.key();
boolean allEntries = annotation.allEntries();
boolean beforeInvocation = annotation.beforeInvocation();
//轉(zhuǎn)換EL表達(dá)式
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
//執(zhí)行方法前清除緩存
if (beforeInvocation) {
//清除全部緩存
deleteRedisCache(cacheName, key, allEntries);
//實(shí)際執(zhí)行的方法
Object proceed = proceedingJoinPoint.proceed();
return proceed;
} else {//成功執(zhí)行方法后清除緩存
//實(shí)際執(zhí)行的方法
Object proceed = proceedingJoinPoint.proceed();
final String cacheNameTemp = cacheName;
final String keyTemp = key;
if (returnTypeName.equals("Flux")) {
return ((Flux) proceed).collectList().doOnNext(list -> {
//清除全部緩存
deleteRedisCache(cacheNameTemp, keyTemp, allEntries);
}).flatMapMany(list -> Flux.fromIterable((List) list));
} else if (returnTypeName.equals("Mono")) {
return ((Mono) proceed).doOnNext(obj -> {
//清除全部緩存
deleteRedisCache(cacheNameTemp, keyTemp, allEntries);
});
} else {
return proceed;
}
}
}
@Around("cachePutPointCut()")
public Object cachePutAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.debug("ReactiveRedisCacheAspect cachePutAround....");
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
String returnTypeName = method.getReturnType().getSimpleName();
ReactiveRedisCachePut annotation = method.getAnnotation(ReactiveRedisCachePut.class);
String cacheName = annotation.cacheName();
String key = annotation.key();
long timeout = annotation.timeout();
//轉(zhuǎn)換EL表達(dá)式
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
String redis_key = cacheName + "_" + key;
boolean hasKey = redisTemplate.hasKey(redis_key);
if (hasKey) {
redisTemplate.delete(redis_key);
}
//實(shí)際執(zhí)行的方法
Object proceed = proceedingJoinPoint.proceed();
if (returnTypeName.equals("Flux")) {
return ((Flux) proceed).collectList().doOnNext(list -> redisTemplate.opsForValue().set(redis_key, list, timeout, TimeUnit.SECONDS)).flatMapMany(list -> Flux.fromIterable((List) list));
} else if (returnTypeName.equals("Mono")) {
return ((Mono) proceed).doOnNext(obj -> redisTemplate.opsForValue().set(redis_key, obj, timeout, TimeUnit.SECONDS));
} else {
return proceed;
}
}
@Around("cachingPointCut()")
public Object cachingAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.debug("ReactiveRedisCacheAspect cachingAround....");
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
String returnTypeName = method.getReturnType().getSimpleName();
ReactiveRedisCaching annotation = method.getAnnotation(ReactiveRedisCaching.class);
ReactiveRedisCacheEvict[] cacheEvicts = annotation.evict();
ReactiveRedisCachePut[] cachePuts = annotation.put();
ReactiveRedisCacheable[] cacheables = annotation.cacheable();
//規(guī)則:
//1.cacheables不能與cacheEvicts或者cachePuts同時(shí)存在,因?yàn)楹笳咭欢〞?huì)執(zhí)行方法主體怎栽,達(dá)不到調(diào)用緩存的目的丽猬,所以當(dāng)cacheables存在時(shí),后者即便指定也不執(zhí)行
//2.先執(zhí)行cacheEvicts婚瓜,再執(zhí)行cachePuts
if (cacheables.length > 0) {
Map<String, Long> key_map = new HashMap<>();
List<String> key_list = new ArrayList<>();
Arrays.stream(cacheables).forEach(cacheable -> {
String cacheName = cacheable.cacheName();
String key = cacheable.key();
long timeout = cacheable.timeout();
//轉(zhuǎn)換EL表達(dá)式
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
String redis_key = cacheName + "_" + key;
key_map.put(redis_key, timeout);
key_list.add(redis_key);
});
AtomicBoolean isAllKeyHas = new AtomicBoolean(true);
key_list.forEach(key -> {
if (!redisTemplate.hasKey(key)) {
isAllKeyHas.set(false);
}
});
//全部key都有值宝鼓,則直接返回緩存
if (isAllKeyHas.get()) {
Object o = redisTemplate.opsForValue().get(key_list.get(0));
if (returnTypeName.equals("Flux")) {
return Flux.fromIterable((List) o);
} else if (returnTypeName.equals("Mono")) {
return Mono.just(o);
} else {
return o;
}
} else {
//實(shí)際執(zhí)行的方法
Object proceed = proceedingJoinPoint.proceed();
if (returnTypeName.equals("Flux")) {
return ((Flux) proceed).collectList()
.doOnNext(list -> key_map.forEach((key, val) -> redisTemplate.opsForValue().set(key, list, val, TimeUnit.SECONDS)))
.flatMapMany(list -> Flux.fromIterable((List) list));
} else if (returnTypeName.equals("Mono")) {
return ((Mono) proceed)
.doOnNext(obj -> key_map.forEach((key, val) -> redisTemplate.opsForValue().set(key, obj, val, TimeUnit.SECONDS)));
} else {
return proceed;
}
}
} else {
Map<String, Boolean> map = new HashMap<>();
if (cacheEvicts.length > 0) {
Arrays.stream(cacheEvicts).forEach(cacheEvict -> {
String cacheName = cacheEvict.cacheName();
String key = cacheEvict.key();
boolean allEntries = cacheEvict.allEntries();
boolean beforeInvocation = cacheEvict.beforeInvocation();
//轉(zhuǎn)換EL表達(dá)式
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
if (beforeInvocation) { //執(zhí)行方法前清除緩存
//清除全部緩存
deleteRedisCache(cacheName, key, allEntries);
} else { //成功執(zhí)行方法后清除緩存,先保存到map中
//清除全部緩存
if (allEntries) {
map.put(cacheName, true);
} else {
map.put(cacheName + "_" + key, false);
}
}
});
}
//實(shí)際執(zhí)行的方法
Object proceed = proceedingJoinPoint.proceed();
if (cachePuts.length > 0) {
Map<String, Long> key_map = new HashMap<>();
Arrays.stream(cachePuts).forEach(cachePut -> {
String cacheName = cachePut.cacheName();
String key = cachePut.key();
long timeout = cachePut.timeout();
//轉(zhuǎn)換EL表達(dá)式
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
String redis_key = cacheName + "_" + key;
key_map.put(redis_key, timeout);
boolean hasKey = redisTemplate.hasKey(redis_key);
if (hasKey) {
redisTemplate.delete(redis_key);
}
});
if (returnTypeName.equals("Flux")) {
return ((Flux) proceed).collectList()
.doOnNext(list -> {
//執(zhí)行方法后清除緩存
if (map.size() > 0) {
map.forEach((key, val) -> {
deleteRedisCache(key, val);
});
}
key_map.forEach((key, val) -> redisTemplate.opsForValue().set(key, list, val, TimeUnit.SECONDS));
})
.flatMapMany(list -> Flux.fromIterable((List) list));
} else if (returnTypeName.equals("Mono")) {
return ((Mono) proceed)
.doOnNext(obj -> {
//執(zhí)行方法后清除緩存
if (map.size() > 0) {
map.forEach((key, val) -> {
deleteRedisCache(key, val);
});
}
key_map.forEach((key, val) -> redisTemplate.opsForValue().set(key, obj, val, TimeUnit.SECONDS));
});
} else {
return proceed;
}
} else {
if (returnTypeName.equals("Flux")) {
return ((Flux) proceed).collectList().doOnNext(list -> {
//執(zhí)行方法后清除緩存
if (map.size() > 0) {
map.forEach((key, val) -> {
deleteRedisCache(key, val);
});
}
}).flatMapMany(list -> Flux.fromIterable((List) list));
} else if (returnTypeName.equals("Mono")) {
return ((Mono) proceed).doOnNext(obj -> {
//執(zhí)行方法后清除緩存
if (map.size() > 0) {
map.forEach((key, val) -> {
deleteRedisCache(key, val);
});
}
});
} else {
return proceed;
}
}
}
}
private void deleteRedisCache(String key, boolean clearAll) {
if (clearAll) {
Set keys = redisTemplate.keys(key + "_*");
if (!keys.isEmpty()) {
redisTemplate.delete(keys);
}
} else {
if (redisTemplate.hasKey(key)) {
redisTemplate.delete(key);
}
}
}
private void deleteRedisCache(String cacheName, String key, boolean clearAll) {
String redis_key = "";
if (clearAll) {
redis_key = cacheName + "_*";
} else {
redis_key = cacheName + "_" + key;
}
deleteRedisCache(redis_key, clearAll);
}
}
注解屬性支持EL表達(dá)式的工具類
AspectSupportUtils
package com.hanqunfeng.reactive.redis.cache.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
public class AspectSupportUtils {
private static ExpressionEvaluator evaluator = new ExpressionEvaluator();
public static Object getKeyValue(JoinPoint joinPoint, String keyExpression) {
if(keyExpression.contains("#") || keyExpression.contains("'")) {
return getKeyValue(joinPoint.getTarget(), joinPoint.getArgs(), joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), keyExpression);
}
return keyExpression;
}
private static Object getKeyValue(Object object, Object[] args, Class<?> clazz, Method method,
String keyExpression) {
if (StringUtils.hasText(keyExpression)) {
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.key(keyExpression, methodKey, evaluationContext);
}
return SimpleKeyGenerator.generateKey(args);
}
}
ExpressionEvaluator
package com.hanqunfeng.reactive.redis.cache.aop;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ExpressionEvaluator extends CachedExpressionEvaluator {
private final ParameterNameDiscoverer paramNameDiscoverer = new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
public EvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method,
Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
ExpressionRootObject root = new ExpressionRootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
public Object key(String conditionExpression, AnnotatedElementKey elementKey, EvaluationContext evalContext) {
return getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
if (targetMethod == null) {
targetMethod = method;
}
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
private class ExpressionRootObject {
private final Object object;
private final Object[] args;
public ExpressionRootObject(Object object, Object[] args) {
this.object = object;
this.args = args;
}
public Object getobject() {
return object;
}
public Object[] getArgs() {
return args;
}
}
}
本項(xiàng)目提供了自動(dòng)配置類巴刻,開(kāi)啟Aspect支持同時(shí)提供RedisTemplate
- 支持LocalDate和LocalDateTime的序列化和反序列化
- 存儲(chǔ)key為字符串,值為json
package com.hanqunfeng.reactive.redis.cache.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @author hanqf
* Created by hanqf on 2020/11/22 15:38
*/
@Configuration
@ComponentScan(basePackages = "org.hanqf.reactive.redis.cache")
@EnableAspectJAutoProxy
@Slf4j
public class ReactiveRedisConfig {
/**
* 默認(rèn)日期時(shí)間格式
*/
public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
/**
* 默認(rèn)日期格式
*/
public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
/**
* 默認(rèn)時(shí)間格式
*/
public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";
@Bean
@ConditionalOnMissingBean(value = RedisTemplate.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
log.debug("ReactiveRedisConfig RedisTemplate");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
//LocalDateTime系列序列化和反序列化模塊蛉签,繼承自jsr310胡陪,我們?cè)谶@里修改了日期格式
JavaTimeModule javaTimeModule = new JavaTimeModule();
//序列化
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
javaTimeModule.addSerializer(LocalTime.class,
new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//反序列化
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
javaTimeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
javaTimeModule.addDeserializer(LocalTime.class,
new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
//注冊(cè)模塊
objectMapper.registerModule(javaTimeModule);
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
serializer.setObjectMapper(objectMapper);
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}