一次扩展SpringCache的@Cacheable注解的尝试(以Redis为例)

简介

如果需要在Springcache中,为单个或一组redis缓存单独设置过期时间,可以在yml配置文件中如下编写:

spring:
  cache:
    cache-names: caches
    redis:
      # 全局缓存过期时间
      time-to-live: 10m
    # 自定义单个缓存name过期时间
    time-to-live:
      # 单个name过期时间
      cache1: 10m
      # 多个name过期时间,key包含特殊符号需要"[]"包裹
      "[cache1,cache2]": 10m

那么,我们可以扩展@Cacheable注解,直接在注解属性上设置过期时间吗?

方案详情—修改源码

步骤

  1. 在@Cacheable注解中添加cacheTime属性
package org.springframework.cache.annotation;
@Target({ElementType.TYPE, ElementType.METHOD})  
@Retention(RetentionPolicy.RUNTIME)  
@Inherited  
@Documented  
@Reflective  
public @interface Cacheable {  
   // ...
  
   // @Modified  
   /**  
    * 过期时间  
    * @return  
    */  
   String cacheTime() default "";
  1. CacheableOperation类中添加cacheTime属性
package org.springframework.cache.interceptor;  
  
import org.springframework.lang.Nullable;  
  
/**  
 * Class describing a cache 'cacheable' operation. * * @author Costin Leau  
 * @author Phillip Webb  
 * @author Marcin Kamionowski  
 * @since 3.1  
 */public class CacheableOperation extends CacheOperation {  
  
   // ...其他属性...
  
   // @Modified1
   @Nullable  
   private final String cacheTime;  
  
   // @Modified2 修改构造
   /**  
    * Create a new {@link CacheableOperation} instance from the given builder.  
    * @since 4.3  
    */   public CacheableOperation(CacheableOperation.Builder b) {  
      super(b);  
      this.unless = b.unless;  
      this.sync = b.sync;  
      // @Modified  
      this.cacheTime = b.cacheTime;  
   }  
  
   // ...其他get方法...
  
   // @Modified3
   public String getCacheTime(){return this.cacheTime;}  
  
   // @Modified4 修改Builder
   /**  
    * A builder that can be used to create a {@link CacheableOperation}.  
    * @since 4.3  
    */   public static class Builder extends CacheOperation.Builder {  
  
      // ...其他属性和setter...

	  // @Modified
      private String cacheTime;   
  
      // @Modified  
      public void setCacheTime(String cacheTime) {  
         this.cacheTime = cacheTime;  
      }  
  
      @Override  
      protected StringBuilder getOperationDescription() {  
         StringBuilder sb = super.getOperationDescription();  
         sb.append(" | unless='");  
         sb.append(this.unless);  
         sb.append('\'');  
         sb.append(" | sync='");  
         sb.append(this.sync);  
         sb.append('\'');  
         // @Modified  
         sb.append(" | cacheTime='");  
         sb.append(this.cacheTime);  
         sb.append('\'');  
         return sb;  
      }  
      
	  // ...
   
   }  
  
}
  1. 修改默认的Parser,即SpringCacheAnnotationParser,从@Cacheable注解注解中读取cacheTime属性后,添加到CacheableOperation类属性
    其实就是在parseCacheableAnnotation加了一句话哈哈
package org.springframework.cache.annotation;  

public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {  
  
   // ...其他方法和属性...
  
   private CacheableOperation parseCacheableAnnotation(  
         AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {  
  
      CacheableOperation.Builder builder = new CacheableOperation.Builder();  
  
      builder.setName(ae.toString());  
      builder.setCacheNames(cacheable.cacheNames());  
      builder.setCondition(cacheable.condition());  
      builder.setUnless(cacheable.unless());  
      builder.setKey(cacheable.key());  
      builder.setKeyGenerator(cacheable.keyGenerator());  
      builder.setCacheManager(cacheable.cacheManager());  
      builder.setCacheResolver(cacheable.cacheResolver());  
      builder.setSync(cacheable.sync());  
  
      // @Modified  
      builder.setCacheTime(cacheable.cacheTime());  
  
      defaultConfig.applyDefault(builder);  
      CacheableOperation op = builder.build();  
      validateCacheOperation(ae, op);  
  
      return op;  
   }  
}
  1. 修改AbstractCacheResolver,在resolveCaches方法中更新过期时间
    *注: StrUtil.parseDuration见附录
package org.springframework.cache.interceptor;  

public abstract class AbstractCacheResolver implements CacheResolver, InitializingBean {  
    // ...其他属性和方法...
  
  
    @Override  
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {  
        Collection<String> cacheNames = getCacheNames(context);  
        if (cacheNames == null) {  
            return Collections.emptyList();  
        }  
        Collection<Cache> result = new ArrayList<>(cacheNames.size());  
        for (String cacheName : cacheNames) {  
            Cache cache = getCacheManager().getCache(cacheName);  
  
            // @Modified  
            // 从@Cacheable获取cacheTime并设置  
            updateCacheTimeFromAnno(context, cache);  
  
            if (cache == null) {  
                throw new IllegalArgumentException("Cannot find cache named '" +  
                        cacheName + "' for " + context.getOperation());  
            }  
            result.add(cache);  
        }  
        return result;  
    }  
  
    // @Modified  
    private void updateCacheTimeFromAnno(CacheOperationInvocationContext<?> context, Cache cache) {  
        BasicOperation basicOperation = context.getOperation();  
        if (!(cache instanceof RedisCache) || !(basicOperation instanceof CacheableOperation)) {  
            return;  
        }  
        RedisCache redisCache = (RedisCache) cache;  
        CacheableOperation cacheableOperation = (CacheableOperation) basicOperation;  
        String cacheTime = cacheableOperation.getCacheTime();  
        // 没有设置cacheTime就不改配置  
        if (!StringUtils.hasText(cacheTime)) {  
            return;  
        }  
        Duration newTtl = StrUtil.parseDuration(cacheTime);  
        RedisCacheConfiguration newConfig = redisCache.getCacheConfiguration().entryTtl(newTtl);  
        // 反射设置私有属性  
        try {  
            Class redisCacheClass = redisCache.getClass();  
            Field cacheConfigurationField = redisCacheClass.getDeclaredField("cacheConfiguration");  
            cacheConfigurationField.setAccessible(true);  
            cacheConfigurationField.set(redisCache, newConfig);  
        } catch (NoSuchFieldException | IllegalAccessException e) {  
            e.printStackTrace();  
        }  
  
  
    }  
  
}

现象

完成以上四部的源码替换后,可以使用下面的方法为单个redis缓存设置超时时间

@Cacheable(value="index", key="'test'", cacheTime = "22d")  
@RestController  
public class MyController {  
    @Cacheable(value="index", key="'test'", cacheTime = "22d")  
    @GetMapping()  
    public String index(){  
        return "index";  
    }  
}

附录

StrUtil工具类parseDuration方法

public static Duration parseDuration(String str){  
    Duration dur;  
  
    String numStr = str.substring(0, str.length() - 1);  
    Integer num = Integer.valueOf(numStr);  
    String unitStr = str.substring(str.length() - 1);  
    String upperCaseUnitStr = unitStr.toUpperCase();  
    switch (upperCaseUnitStr) {  
        case "S":  
            dur = Duration.ofSeconds(num);  
            break;        case "M":  
            dur = Duration.ofMinutes(num);  
            break;        case "H":  
            dur = Duration.ofHours(num);  
            break;        case "D":  
            dur = Duration.ofDays(num);  
            break;        default:  
            throw new BusinessException("不支持的时间单位"+upperCaseUnitStr);  
    }  
    return dur;  
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值