spring cache实现自定义redis过期时间

前一篇文章(spring cache设置指定Key过期时间)已经提到过怎么实现spring cache自定义过期时间,但是拿来使用后,被吐槽不够优雅(也是醉了),也对,身为一个有追(不)求(服)心态的程序猿,就应该做到更好。
重新梳理后,大概思路是这样的:

  1. 自定义一个注解@TimeToLive,包括过期时间、和时间单位
  2. 方法上增加自定义注解@TimeToLive
  3. 定义一个切面,利用spring AOP原理,pointcut含有此自定义注解的方法
  4. 通过AOP切面拿到cacheName和Method的映射关系,缓存到本地
  5. 自定义CustomizerRedisCacheManager覆盖RedisCacheManager的computeExpiration方法

以下为代码部分:

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * @Description Redis过期时间注解
 * @Author huangd
 * @Date 2020-01-02
 **/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimeToLive {

    long ttl() default 60 * 60;

    TimeUnit timeUnit() default TimeUnit.SECONDS;

}

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class VehicleSeriesCacheService {

    @Autowired
    private XXXMapper xxxMapper;

    @Cacheable(value = "ota.vehicle.series", key = "#id + ''")
    **@TimeToLive(ttl = 20, timeUnit = TimeUnit.MINUTES)**
    public VehicleSeries fetchById(Long id) {
        return xxxMapper.fetch(id);
    }
}
import com.mng.boot.common.annotation.TimeToLive;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * @Description Spring cache 过期时间AOP处理
 * @Author huangd
 * @Date 2020-01-02
 **/
@Aspect
@Component
public class CacheTtlAdvice implements Ordered {
    private HashMap<String, Method> cacheNameMethodCache = new HashMap<>(16);

    /**
     * 初始化过期时间配置
     * @param joinPoint 切入点
     */
    @Before("@annotation(com.xiaopeng.ota.mng.boot.common.annotation.TimeToLive)")
    public void before(JoinPoint joinPoint){
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        TimeToLive timeToLive = AnnotationUtils.findAnnotation(method, TimeToLive.class);
        Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);

        if(null != cacheable && cacheable.cacheNames().length > 0){
            String[] cacheNames = cacheable.cacheNames();
            if(timeToLive != null){
                for(String cacheName : cacheNames){
                    if(!cacheNameMethodCache.containsKey(cacheName)){
                        cacheNameMethodCache.put(cacheName, method);
                    }
                }
            }
        }
    }

    @Override
    public int getOrder() {
        return 1000;
    }
    
    public HashMap<String, Method> getCacheNameMethodCache() {
        return cacheNameMethodCache;
    }
}

@Slf4j
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis")
@Service
public class CustomizerRedisCacheManager extends RedisCacheManager {

    @Autowired
    private CacheTtlAdvice cacheTtlAdvice;

    public CustomizerRedisCacheManager(@Qualifier("redisTemplate") RedisOperations redisOperations) {
        super(redisOperations);
    }

    @Override
    public long computeExpiration(String name) {
        long defaultExpiration = super.computeExpiration(name);
        Method method = cacheTtlAdvice.getCacheNameMethodCache().get(name);
        if (method != null) {
            TimeToLive timeToLive = AnnotationUtils.findAnnotation(method, TimeToLive.class);
            return timeToLive.timeUnit().toSeconds(timeToLive.ttl());
        }

        return defaultExpiration;
    }
}

配置RedisTemplate的key为StringRedisSerializer序列化方式(默认JDK序列化)

@Configuration
public class CustomCachingConfigurerSupport extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

说明:CacheTtlAdvice 切面类必须实现Ordered 接口并实现getOrder方法,因为@Cacheable也是通过AOP来实现的,会涉及到优先级问题,getOrder值越小越先执行,默认为(2147483647)可以查看@EnableCaching的order默认值,这里我们自定义的注解必须先执行。

猜你喜欢

转载自blog.csdn.net/huangdi1309/article/details/103831716
今日推荐