简介
如果需要在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注解,直接在注解属性上设置过期时间吗?
方案详情—修改源码
步骤
- 在@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 "";
- 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;
}
// ...
}
}
- 修改默认的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;
}
}
- 修改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;
}