spring cache ttl 过期实现
一般的,使用 spring cache 时,注解上不支持 ttl 过期时间
@Cacheable(cacheNames ="product3", key ="#id")@GetMapping("/product3/{id}")publicStringgetById3(@PathVariable("id")Integer id){
log.info("get from db");return"success";}
虽然可以在 配置 CacheManager 时进行配置,但每次调整都需要修改配置 文件
@BeanpublicCacheManagercacheManager(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序列化器returnRedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(lettuceConnectionFactory)).cacheDefaults(redisCacheConfiguration).build();}
那如何实现注解上设置ttl呢?
实现
思路:
- 继承 Cacheable 注解
- 容器刷新后重新刷新
acheManager.initializeCaches()
方法,重新加载
定义注解 @TTLCacheable
继承 @Cacheable 注解
@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Inherited@Cacheable@Documentedpublic@interfaceTTLCacheable{@AliasFor(annotation =Cacheable.class, value ="value")String[]value()default{};@AliasFor(annotation =Cacheable.class, value ="cacheNames")String[]cacheNames()default{};@AliasFor(annotation =Cacheable.class, value ="key")Stringkey()default"";@AliasFor(annotation =Cacheable.class, value ="keyGenerator")StringkeyGenerator()default"";@AliasFor(annotation =Cacheable.class, value ="cacheResolver")StringcacheResolver()default"";@AliasFor(annotation =Cacheable.class, value ="condition")Stringcondition()default"";@AliasFor(annotation =Cacheable.class, value ="unless")Stringunless()default"";@AliasFor(annotation =Cacheable.class, value ="sync")booleansync()defaultfalse;/**
* cache 过期时间
* @return
*/intttl()default0;}
刷新ttl时间 TTLCachePostProcessor
publicclassTTLCachePostProcessorimplementsBeanPostProcessor,SmartInitializingSingleton,BeanFactoryAware{privateBeanFactory beanFactory;privatestaticSet<TTLCacheable> cacheables =newConcurrentHashSet<>();privateRedisTTLCacheConfig redisTTLCacheConfig;publicTTLCachePostProcessor(RedisTTLCacheConfig redisTTLCacheConfig){this.redisTTLCacheConfig = redisTTLCacheConfig;}@OverridepublicvoidafterSingletonsInstantiated(){if(!CollectionUtils.isEmpty(cacheables)){CacheManager cm = beanFactory.getBean(CacheManager.class);if(!(cm instanceofRedisCacheManager)){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 =newHashSet<>();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();}}@OverridepublicObjectpostProcessBeforeInitialization(Object bean,String beanName)throwsBeansException{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;}@OverridepublicvoidsetBeanFactory(BeanFactory beanFactory)throwsBeansException{this.beanFactory = beanFactory;}}
RedisTTLCacheConfig:
@DatapublicclassRedisTTLCacheConfig{/**
* Entry expiration. By default the entries never expire.
*/privateDuration timeToLive;/**
* Allow caching null values.
*/privateboolean cacheNullValues =true;/**
* Key prefix.
*/privateString keyPrefix;/**
* Whether to use the key prefix when writing to Redis.
*/privateboolean useKeyPrefix =true;privateConversionService conversionService;/**
* 序列化
*/privateRedisSerializationContext.SerializationPair<String> keySerializationPair;privateRedisSerializationContext.SerializationPair<Object> valueSerializationPair;}
自动装配
- @EnableCacheTTLOperation
- CacheTTLAutoConfiguration
- CacheTTLOperationConfigSelector
@EnableCacheTTLOperation
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(CacheTTLOperationConfigSelector.class)public@interfaceEnableCacheTTLOperation{}
CacheTTLAutoConfiguration
@Configuration@ConditionalOnClass(value ={RedisTemplate.class,CacheInterceptor.class})@ConditionalOnBean(RedisTemplate.class)@EnableConfigurationProperties(value ={CacheProperties.class})publicclassCacheTTLAutoConfiguration{@Bean@ConditionalOnMissingBean(RedisTTLCacheConfig.class)publicRedisTTLCacheConfigredisTTLCacheConfig(CacheProperties properties,RedisTemplate redisTemplate){CacheProperties.Redis redisProperties = properties.getRedis();RedisTTLCacheConfig config =newRedisTTLCacheConfig();
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;}@BeanpublicTTLCachePostProcessorttlCachePostProcessor(ObjectProvider<RedisTTLCacheConfig> provider){returnnewTTLCachePostProcessor(provider.getIfAvailable());}}
CacheTTLOperationConfigSelector
publicclassCacheTTLOperationConfigSelectorimplementsImportSelector{@OverridepublicString[]selectImports(AnnotationMetadata importingClassMetadata){returnnewString[]{"com.x.x.CacheTTLAutoConfiguration"};}}
测试
- 在 boot 启动类加上注解
@EnableCacheTTLOperation
- 使用 @TTLCacheable 设置 过期时间
@EnableCaching@EnableCacheTTLOperation@SpringBootApplicationpublicclassMvcApplication{//omit...}
Controller
@Slf4j@RestControllerpublicclassCacheController{@TTLCacheable(cacheNames ="product", key ="#id", ttl =120)@GetMapping("/product/{id}")publicStringgetById(@PathVariable("id")Integer id){
log.info("get from db");return"success";}@TTLCacheable(cacheNames ="product2", key ="#id")@GetMapping("/product2/{id}")publicStringgetById2(@PathVariable("id")Integer id){
log.info("get from db");return"success";}@Cacheable(cacheNames ="product3", key ="#id")@GetMapping("/product3/{id}")publicStringgetById3(@PathVariable("id")Integer id){
log.info("get from db");return"success";}}
good luck!
版权归原作者 iiaythi 所有, 如有侵权,请联系我们删除。