默认SpringCache是不支持自定义设置TTL的,redis的只支持配置一个统一的TTL,但是这可能并不符合业务的需求。
下面开始增强SpringCache让它支持配置化TTL
定义CachceTTL注解
/**
* 缓存有效时长, 单位: 秒, 若写在类上则类中所有方法都继承此时间
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheTTL {
/**
* 缓存有效时长, 单位: 秒
*/
long value() default -1;
}
实战
缓存
/**
* 生成验证码图片, 返回验证码文字, 并把验证码文字缓存
*/
@CacheTTL(120L)
@CachePut(key = "#token", unless = "#result == null")
public String generateCaptchaImage(String token, OutputStream os) throws IOException {
String captchaText = ImageCaptchaGenerator.generateCaptchaText(candidateString, 4);
ImageCaptchaGenerator.writeCaptchaImageToOutputStream(captchaText, width, height, os);
return captchaText;
}
移除缓存
/**
* 移除缓存
*/
@CacheTTL(120L)
@CacheEvict(key = "#token")
public void removeCaptchaCodeCache(String token) { }
实现原理
- 使用AOP拦截CacheTTL注解的方法或类里的所有方法
- 在写入redis之前把TTL信息保存到ThreadLocal中
- 在redis写入时取出ThreadLocal中的TTL并设置
Aop拦截并保存TTL
@Component
@Aspect
@Order(-1) // 需要比redis serializer 提前执行
public class CacheTTLAop {
@Around("@annotation(org.cloud.cache.CacheTTL) || @within(org.cloud.cache.CacheTTL)")
public Object run(JoinPoint joinPoint) throws Throwable {
try {
Signature signature = joinPoint.getSignature();
if (signature instanceof MethodSignature methodSignature) {
Method method = methodSignature.getMethod();
CacheTTL expireAt = method.getAnnotation(CacheTTL.class);
if (expireAt == null) {
Class<?> declaringType = methodSignature.getDeclaringType();
expireAt = declaringType.getAnnotation(CacheTTL.class);
}
long ttl = expireAt.value();
CacheTTLContext.setTTL(ttl);
}
return ((MethodInvocationProceedingJoinPoint) joinPoint).proceed(joinPoint.getArgs());
} finally {
CacheTTLContext.remove();
}
}
}
redis写入
public class CustomRedisCache extends RedisCache {
private final RedisCacheWriter cacheWriter;
private final String name;
private final RedisCacheConfiguration cacheConfig;
// ... 省略其他无关方法完整源码请看https://github.com/L1yp/van-template-api/blob/main/src/main/java/org/cloud/cache/custom/CustomRedisCache.java
@Override
public void put(@NotNull Object key, Object value) {
Object cacheValue = preProcessCacheValue(value);
if (!isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
name));
}
// add expire ttl
Duration ttl = cacheConfig.getTtl();
// 获取ThreadLocal上下文中的TTL
Long ctxTTL = CacheTTLContext.getTTL();
if (ctxTTL != null && ctxTTL > 0) {
ttl = Duration.ofSeconds(ctxTTL);
}
this.cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), ttl);
}
@Override
public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
Object cacheValue = preProcessCacheValue(value);
if (!isAllowNullValues() && cacheValue == null) {
return get(key);
}
// add expire ttl
Duration ttl = cacheConfig.getTtl();
// 获取ThreadLocal上下文中的TTL
Long ctxTTL = CacheTTLContext.getTTL();
if (ctxTTL != null && ctxTTL > 0) {
ttl = Duration.ofSeconds(ctxTTL);
}
byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue),
ttl);
if (result == null) {
return null;
}
return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result)));
}
}
配置CustomRedisCache
自定义RedisCacheManager
/**
* 支持可配置化的本地缓存
*/
public class CustomRedisCacheManager extends RedisCacheManager {
private final RedisCacheWriter cacheWriter;
private final RedisCacheConfiguration defaultCacheConfiguration;
public CustomRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
this.cacheWriter = cacheWriter;
this.defaultCacheConfiguration = defaultCacheConfiguration;
}
@Override
@NotNull
protected RedisCache createRedisCache(@NotNull String name, RedisCacheConfiguration cacheConfig) {
return new CustomRedisCache(name, this.cacheWriter, cacheConfig != null ? cacheConfig : this.defaultCacheConfiguration);
}
}
配置自定义 RedisCacheManager
@Bean
@Primary
public CustomRedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory, RedisCacheConfiguration redisCacheConfiguration) {
// 使用分布式锁
return new CustomRedisCacheManager(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory), redisCacheConfiguration);
}
下一篇: ④优雅的缓存框架:SpringCache之多级缓存
以上代码来源: 后端代码:github.com/L1yp/van-te…
点击链接加入群聊:【Van交流群】