SpringBoot-WebFlux-Redis缓存注解

摘要

  • 通过本文,你将知道如何在WebFlux项目中通过redis注解缓存方法的返回值
  • 本项目基于springboot:2.4.0,jdk1.8,并使用Maven构建
  • 代码地址:https://github.com/hanqunfeng/reactive-redis-cache-annotation-spring-boot-starter

前言

最近在使用WebFlux时发现,SpringBoot提供的@Cacheable,@CachePut,@CacheEvict和@Caching注解不支持响应式方法,SpringBoot官方也没有提供响应式方法的缓存注解,看到网上的一些解决方案都是直接在方法代码中加入缓存数据的代码逻辑,这样虽然可以解决问题,但是代码侵入并不优雅,于是萌生自己写一个基于redis的响应式方法缓存注解的想法,本项目参考SpringBoot提供的@Cacheable,@CachePut,@CacheEvict和@Caching注解声明,但是只是实现了一些基本功能,可以满足绝大部分使用场景的要求,因为SpringBoot早晚会给出官方解决方案,在此之前,不妨一试。

使用示例

  • 本项目已经发布到maven中央仓库,直接在项目中添加依赖即可。
  • 本项目虽然基于springboot:2.4.0构建,但实际上springboot2.0+都可以使用。
  • maven依赖

  com.hanqunfeng
  reactive-redis-cache-annotation-spring-boot-starter
  1.1.0

  • gradle依赖
implementation 'com.hanqunfeng:reactive-redis-cache-annotation-spring-boot-starter:1.1.0'
  • 此时项目中可能还要添加其它依赖,以gradle举例
//webflux,非必须,主要是面向响应式编程的,所以使用springboot大概率会使用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连接池,非必须,正式环境建议开启连接池
implementation 'org.apache.commons:commons-pool2'

//aop 面向方面编程 支持@Aspect,非必须
implementation 'org.springframework.boot:spring-boot-starter-aop'
  • 方法返回值必须是Mono或者Flux类型,使用方式与springboot提供的Cacheable等注解类似
    /**
    * 缓存 cacheName和key支持EL表达式,实际key的名称是"cacheName_key"
    * 缓存结果
    * 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 findUserByUsername(String username) {
        return sysUserRepository.findByUsername(username);
    }

    @ReactiveRedisCacheable(cacheName = "sysuser", key = "all")
    public Flux findAll() {
        return sysUserRepository.findAll();
    }

    /**
    * 删除缓存,allEntries = true 表示删除全部以"cacheName_"开头的缓存
    * allEntries 默认false,此时需要指定key的值,表示删除指定的"cacheName_key"
    */
    @ReactiveRedisCacheEvict(cacheName = "sysuser", allEntries = true)
    public Mono add(SysUser sysUser) {
        return sysUserRepository.addSysUser(sysUser.getId(), sysUser.getUsername(), sysUser.getPassword(), sysUser.getEnable()).flatMap(data -> sysUserRepository.findById(sysUser.getId()));
    }

    /**
    * 组合注解,用法与@Caching类似
    * 规则:
    * 1.cacheables不能与cacheEvicts或者cachePuts同时存在,因为后者一定会执行方法主体,达不到调用缓存的目的,所以当cacheables存在时,后者即便指定也不执行
    * 2.先执行cacheEvicts,再执行cachePuts
    */
    @ReactiveRedisCaching(
            evict = {@ReactiveRedisCacheEvict(cacheName = "sysuser", key = "all")},
            put = {@ReactiveRedisCachePut(cacheName = "sysuser", key = "'find_' + #sysUser.username")}
    )
    public Mono update(SysUser sysUser) {
        Mono save = sysUserRepository.save(sysUser);
        return save;
    }

    /**
    * 删除指定的"cacheName_key"
    */
    @ReactiveRedisCacheEvict(cacheName = "sysuser", key="'find_' + #username")
    public Mono deleteByUserName(String username) {
        return sysUserRepository.deleteByUsername(username);
    }

RedisTemplate

  • 如果使用时没有创建RedisTemplate,本项目中提供了一个默认的RedisTemplate,基于jackson序列化,支持jdk8的LocalDate和LocalDateTime
    @Bean
    @ConditionalOnMissingBean(value = RedisTemplate.class)
    public RedisTemplate 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,我们在这里修改了日期格式
        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)));

        //注册模块
        objectMapper.registerModule(javaTimeModule);

        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        serializer.setObjectMapper(objectMapper);

        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }

 
 

以下为源码说明

源码依赖


    org.springframework.boot
    spring-boot-autoconfigure


    org.springframework.boot
    spring-boot-configuration-processor


    com.fasterxml.jackson.core
    jackson-databind


    com.fasterxml.jackson.datatype
    jackson-datatype-jdk8


    com.fasterxml.jackson.datatype
    jackson-datatype-jsr310


    org.aspectj
    aspectjweaver


    io.projectreactor
    reactor-core


    org.springframework.data
    spring-data-redis


    org.projectlombok
    lombok
    true


自定义Redis缓存相关注解

  • 只支持方法返回类型为Mono或者Flux
  • 其它返回类型时请使用springboot提供的Cacheable,CachePut,CacheEvict和Caching
  • 使用方式与springboot提供的Cacheable,CachePut,CacheEvict和Caching类似,具体看本文上面的示例

ReactiveRedisCacheable

package com.hanqunfeng.reactive.redis.cache.aop;

import java.lang.annotation.*;

/**
 * 

redis方法缓存注解

* Created by hanqf on 2020/11/21 18:28. */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ReactiveRedisCacheable { /** * 缓存key,key为cacheName+"_"+key * 支持EL表达式 */ String key(); /** * 缓存key分组,会做为缓存key的前缀+"_" * 支持EL表达式 */ String cacheName(); /** * 缓存过期时间,单位秒,默认24小时 */ long timeout() default 24 * 3600L; }

ReactiveRedisCacheEvict

package com.hanqunfeng.reactive.redis.cache.aop;

import java.lang.annotation.*;

/**
 * 

redis清除缓存注解

* 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表达式 */ String key() default ""; /** * 缓存key分组,会做为缓存key的前缀+"_" * 支持EL表达式 */ String cacheName(); /** * 是否删除cacheName下全部缓存数据, * true时cacheName不能为空,此时即便指定了key值,也会删除cacheName下全部缓存 * false时key值不能为空 */ boolean allEntries() default false; /** * 调用清除缓存的时机,true:执行方法前,false:执行方法后 * 如果是false,则方法执行过程中发生异常,则不会清除缓存 */ boolean beforeInvocation() default false; }

ReactiveRedisCachePut

package com.hanqunfeng.reactive.redis.cache.aop;

import java.lang.annotation.*;

/**
 * 

执行完方法更新缓存

* Created by hanqf on 2020/11/21 23:15. */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ReactiveRedisCachePut { /** * 缓存key,key为cacheName+"_"+key * 支持EL表达式 */ String key(); /** * 缓存key分组,会做为缓存key的前缀+"_" * 支持EL表达式 */ String cacheName(); /** * 缓存过期时间,单位秒,默认24小时 */ long timeout() default 24 * 3600L; }

ReactiveRedisCaching

package com.hanqunfeng.reactive.redis.cache.aop;

import java.lang.annotation.*;

/**
 * 

组合

* 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;

/**
 * 

redis缓存aop

* Created by hanqf on 2020/11/21 16:16. */ @Component //标识是一个Aspect代理类 @Aspect //如果有多个切面拦截相同的切点,可以用@Order指定执行顺序 //@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() { } //环绕通知,一般不建议使用,可以通过@Before和@AfterReturning实现 //但是响应式方法只能通过环绕通知实现aop,因为其它通知会导致不再同一个线程执行 @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(); //转换EL表达式 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 { //实际执行的方法 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(); //转换EL表达式 cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName); key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key); //执行方法前清除缓存 if (beforeInvocation) { //清除全部缓存 deleteRedisCache(cacheName, key, allEntries); //实际执行的方法 Object proceed = proceedingJoinPoint.proceed(); return proceed; } else {//成功执行方法后清除缓存 //实际执行的方法 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(); //转换EL表达式 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); } //实际执行的方法 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(); //规则: //1.cacheables不能与cacheEvicts或者cachePuts同时存在,因为后者一定会执行方法主体,达不到调用缓存的目的,所以当cacheables存在时,后者即便指定也不执行 //2.先执行cacheEvicts,再执行cachePuts if (cacheables.length > 0) { Map key_map = new HashMap<>(); List key_list = new ArrayList<>(); Arrays.stream(cacheables).forEach(cacheable -> { String cacheName = cacheable.cacheName(); String key = cacheable.key(); long timeout = cacheable.timeout(); //转换EL表达式 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 { //实际执行的方法 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 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(); //转换EL表达式 cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName); key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key); if (beforeInvocation) { //执行方法前清除缓存 //清除全部缓存 deleteRedisCache(cacheName, key, allEntries); } else { //成功执行方法后清除缓存,先保存到map中 //清除全部缓存 if (allEntries) { map.put(cacheName, true); } else { map.put(cacheName + "_" + key, false); } } }); } //实际执行的方法 Object proceed = proceedingJoinPoint.proceed(); if (cachePuts.length > 0) { Map key_map = new HashMap<>(); Arrays.stream(cachePuts).forEach(cachePut -> { String cacheName = cachePut.cacheName(); String key = cachePut.key(); long timeout = cachePut.timeout(); //转换EL表达式 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 -> { //执行方法后清除缓存 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 -> { //执行方法后清除缓存 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 -> { //执行方法后清除缓存 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 -> { //执行方法后清除缓存 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表达式的工具类

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 conditionCache = new ConcurrentHashMap<>(64);

    private final Map 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;
        }

    }
}

本项目提供了自动配置类,开启Aspect支持同时提供RedisTemplate

  • 支持LocalDate和LocalDateTime的序列化和反序列化
  • 存储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 {

    /**
     * 默认日期时间格式
     */
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    /**
     * 默认日期格式
     */
    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    /**
     * 默认时间格式
     */
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";


    @Bean
    @ConditionalOnMissingBean(value = RedisTemplate.class)
    public RedisTemplate 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,我们在这里修改了日期格式
        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)));

        //注册模块
        objectMapper.registerModule(javaTimeModule);

        Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        serializer.setObjectMapper(objectMapper);

        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}
 

                            
                        
                    
                    
                    

你可能感兴趣的:(SpringBoot-WebFlux-Redis缓存注解)