spring cache ttl 过期

spring cache ttl 过期实现

一般的,使用 spring cache 时,注解上不支持 ttl 过期时间

@Cacheable(cacheNames = "product3", key = "#id")
@GetMapping("/product3/{id}")
public String getById3(@PathVariable("id") Integer id) {
    log.info("get from db");
    return "success";
}

虽然可以在 配置 CacheManager 时进行配置,但每次调整都需要修改配置 文件

@Bean 
public CacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory ) {
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
    redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) // 设置缓存的默认超时时间:30分钟
        .disableCachingNullValues() // 如果是空值,不缓存
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string())) // 设置key序列化器
        .serializeValuesWith(
        RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.java())); // 设置value序列化器
    return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(lettuceConnectionFactory))
        .cacheDefaults(redisCacheConfiguration).build();
}

那如何实现注解上设置ttl呢?

实现

思路:

  • 继承 Cacheable 注解
  • 容器刷新后重新刷新 acheManager.initializeCaches() 方法,重新加载

定义注解 @TTLCacheable

继承 @Cacheable 注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Cacheable
@Documented
public @interface TTLCacheable {

    @AliasFor(annotation = Cacheable.class, value = "value")
    String[] value() default {};

    @AliasFor(annotation = Cacheable.class, value = "cacheNames")
    String[] cacheNames() default {};

    @AliasFor(annotation = Cacheable.class, value = "key")
    String key() default "";

    @AliasFor(annotation = Cacheable.class, value = "keyGenerator")
    String keyGenerator() default "";

    @AliasFor(annotation = Cacheable.class, value = "cacheResolver")
    String cacheResolver() default "";

    @AliasFor(annotation = Cacheable.class, value = "condition")
    String condition() default "";

    @AliasFor(annotation = Cacheable.class, value = "unless")
    String unless() default "";

    @AliasFor(annotation = Cacheable.class, value = "sync")
    boolean sync() default false;

    /**
     * cache 过期时间
     * @return
     */
    int ttl() default 0;
}

刷新ttl时间 TTLCachePostProcessor

public class TTLCachePostProcessor implements BeanPostProcessor, SmartInitializingSingleton, BeanFactoryAware {

    private BeanFactory beanFactory;

    private static Set<TTLCacheable> cacheables = new ConcurrentHashSet<>();
    private RedisTTLCacheConfig redisTTLCacheConfig;

    public TTLCachePostProcessor(RedisTTLCacheConfig redisTTLCacheConfig) {
        this.redisTTLCacheConfig = redisTTLCacheConfig;
    }

    @Override
    public void afterSingletonsInstantiated() {
        if (!CollectionUtils.isEmpty(cacheables)) {
            CacheManager cm = beanFactory.getBean(CacheManager.class);
            if (!(cm instanceof RedisCacheManager)){
                return;
            }
            RedisCacheManager cacheManager = (RedisCacheManager) cm;
            //反射
            Field field = ReflectUtil.getField(RedisCacheManager.class, "initialCacheConfiguration");
            field.setAccessible(Boolean.TRUE.booleanValue());
            Map<String, RedisCacheConfiguration> configMap = (Map<String, RedisCacheConfiguration>) ReflectionUtils.getField(field, cacheManager);
            Set<String> list = new HashSet<>();
            for (TTLCacheable cacheable : cacheables) {
                for (String cache : cacheable.cacheNames()) {
                    if (!list.contains(cache)) {
                        list.add(cache);
                        if (redisTTLCacheConfig!=null){
                            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
                            config = config.serializeValuesWith(redisTTLCacheConfig.getValueSerializationPair());
                            config = config.serializeKeysWith(redisTTLCacheConfig.getKeySerializationPair());
                            if (redisTTLCacheConfig.getKeyPrefix() != null) {
                                config = config.computePrefixWith(cacheName -> cacheName.concat(":").concat(redisTTLCacheConfig.getKeyPrefix()).concat(":"));
                            }
                            if (!redisTTLCacheConfig.isCacheNullValues()) {
                                config = config.disableCachingNullValues();
                            }
                            if (!redisTTLCacheConfig.isUseKeyPrefix()) {
                                config = config.disableKeyPrefix();
                            }
                            config = config.entryTtl(Duration.ofSeconds(cacheable.ttl()));
                            configMap.put(cache, config);
                        }else {
                            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
                            config = config.entryTtl(Duration.ofSeconds(cacheable.ttl()));
                            configMap.put(cache, config);
                        }
                    }
                }
            }
            cacheManager.initializeCaches();
        }
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
        if (AnnotationUtils.isCandidateClass(targetClass, Arrays.asList(TTLCacheable.class))) {
            Map<Method, TTLCacheable> annotatedMethods =
                    MethodIntrospector.selectMethods(targetClass, (MethodIntrospector.MetadataLookup<TTLCacheable>) method -> {
                        TTLCacheable ttlCacheables = AnnotatedElementUtils.getMergedAnnotation(method, TTLCacheable.class);
                        return ttlCacheables;
                    });
            if (!annotatedMethods.isEmpty()) {
                for (Map.Entry<Method, TTLCacheable> methodSetEntry : annotatedMethods.entrySet()) {
                    if (methodSetEntry.getValue().ttl() > 0) {
                        cacheables.add(methodSetEntry.getValue());
                    }
                }
            }
        }
        return bean;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}

RedisTTLCacheConfig:

@Data
public class RedisTTLCacheConfig{
    /**
     * Entry expiration. By default the entries never expire.
     */
    private Duration timeToLive;

    /**
     * Allow caching null values.
     */
    private boolean cacheNullValues = true;

    /**
     * Key prefix.
     */
    private String keyPrefix;

    /**
     * Whether to use the key prefix when writing to Redis.
     */
    private boolean useKeyPrefix = true;
    private ConversionService conversionService;
    /**
     * 序列化
     */
    private RedisSerializationContext.SerializationPair<String> keySerializationPair;
    private RedisSerializationContext.SerializationPair<Object> valueSerializationPair;

}

自动装配

  • @EnableCacheTTLOperation
  • CacheTTLAutoConfiguration
  • CacheTTLOperationConfigSelector
@EnableCacheTTLOperation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CacheTTLOperationConfigSelector.class)
public @interface EnableCacheTTLOperation {

}
CacheTTLAutoConfiguration
@Configuration
@ConditionalOnClass(value = {RedisTemplate.class, CacheInterceptor.class})
@ConditionalOnBean(RedisTemplate.class)
@EnableConfigurationProperties(value = {CacheProperties.class})
public class CacheTTLAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(RedisTTLCacheConfig.class)
    public RedisTTLCacheConfig redisTTLCacheConfig(CacheProperties properties, RedisTemplate redisTemplate) {
        CacheProperties.Redis redisProperties = properties.getRedis();
        RedisTTLCacheConfig config = new RedisTTLCacheConfig();
        config.setTimeToLive(redisProperties.getTimeToLive());
        config.setCacheNullValues(redisProperties.isCacheNullValues());
        config.setKeyPrefix(redisProperties.getKeyPrefix());
        config.setUseKeyPrefix(redisProperties.isUseKeyPrefix());
        config.setKeySerializationPair(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getKeySerializer()));
        config.setValueSerializationPair(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return config;
    }

    @Bean
    public TTLCachePostProcessor ttlCachePostProcessor(ObjectProvider<RedisTTLCacheConfig> provider) {
        return new TTLCachePostProcessor(provider.getIfAvailable());
    }
}

CacheTTLOperationConfigSelector
public class CacheTTLOperationConfigSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.x.x.CacheTTLAutoConfiguration"};
    }

}

测试

  • 在 boot 启动类加上注解 @EnableCacheTTLOperation
  • 使用 @TTLCacheable 设置 过期时间
@EnableCaching
@EnableCacheTTLOperation
@SpringBootApplication
public class MvcApplication {
    //omit...
}

Controller

@Slf4j
@RestController
public class CacheController {


    @TTLCacheable(cacheNames = "product", key = "#id", ttl = 120)
    @GetMapping("/product/{id}")
    public String getById(@PathVariable("id") Integer id) {
        log.info("get from db");
        return "success";
    }

    @TTLCacheable(cacheNames = "product2", key = "#id")
    @GetMapping("/product2/{id}")
    public String getById2(@PathVariable("id") Integer id) {
        log.info("get from db");
        return "success";
    }

    @Cacheable(cacheNames = "product3", key = "#id")
    @GetMapping("/product3/{id}")
    public String getById3(@PathVariable("id") Integer id) {
        log.info("get from db");
        return "success";
    }

}

good luck!

你可能感兴趣的:(springmvc,cache)