核心实现思想
利用Java继承的特性,覆盖Spring默认获取缓存的方法,增加有效期验证。
实现步骤
步骤1:继承 Spring 默认缓存管理器ConcurrentMapManage
@Component
@EnableCaching
public class ConcurrentTokenCacheManager extends ConcurrentMapCacheManager {
private SerializationDelegate serialization;
@PostConstruct
public void initSerialization() {
Field serialization = ReflectionUtils.findField(ConcurrentMapCacheManager.class, "serialization");
ReflectionUtils.makeAccessible(serialization);
this.serialization = (SerializationDelegate) ReflectionUtils.getField(serialization, this);
}
@Override
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = this.isStoreByValue() ? this.serialization : null;
return new ConcurrentTokenCache(name, new ConcurrentHashMap(256), this.isAllowNullValues(), actualSerialization);
}
}
1、利用@PostConstruct实现Bean初始化后调用特性,通过反射拿到ConcurrentMapCacheManager初始化的SerializationDelegate序列化对象。(由于ConcurrentMapCacheManager未提供SerializationDelegate的访问权限,所以此处利用反射机制修复属性访问权限,核心代码:ReflectionUtils.makeAccessible(serialization);)
2、覆盖缓存管理器方法 createConcurrentMapCache实现自定义Cache。
步骤2:继承Spring默认缓存实现ConcurrentMapCache
public class ConcurrentTokenCache extends ConcurrentMapCache {
public ConcurrentTokenCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues, @Nullable SerializationDelegate serialization) {
super(name, store, allowNullValues, serialization);
}
@Override
protected Object lookup(Object key) {
Object lookup = super.lookup(key);
if (lookup instanceof CacheToken && ((CacheToken) lookup).isExpiration()) {
log.info("Token is Expiration !");
super.evict(key);
return null;
}
return lookup;
}
@Override
public void put(Object key, Object value) {
log.info("Put Cache {} = {}", key, value);
super.put(key, value);
}
}
public abstract class CacheToken {
private String token;
private Date expirationTime;
/**
* 校验是否失效
* @return true 已失效 | false 未失效
*/
public boolean isExpiration() {
return expirationTime.before(new Date());
}
}
覆盖ConcurrentMapCache.lookup方法,此处实现Token的有效期验证。其中CacheToken为自定义抽象对象,由缓存业务对象继承。
覆盖ConcurrentMapCache.put方法,由于缓存属于被动失效模式,即获取时才验证缓存是否过期,如果系统中缓存数据太多会对本地内存造成不必要的浪费,因此可以通过覆盖put方法增加定时任务机制,定时清理过期缓存,达到性能上优化。
实战测试
@Cacheable(cacheManager = "concurrentTokenCacheManager", cacheNames = "CompanyToken", key = "#businessId")
public MallToken getCompanyToken(String businessId, String clientId, String clientSecret) {
log.info("创建Token");
MallToken token = new MallToken();
token.setToken("xxxxx");
LocalDateTime dateTime = LocalDateTime.now().plusSeconds(10);
token.setExpirationTime(Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant()));
return token;
}
PS: cacheNames 必填,否则会报错。cacheManager 可以不用设置,当系统有多个缓存管理器时最好指定一个管理器。
文章知识点总结
继承:子类可访问父类非私有的属性和方法。
反射:通过Java反射机制修改对象私有属性或方法的访问权限,获取或设置对象私有属性或方法。