接SpringBoot之redis缓存的基本配置及使用一文,我们可以使用@Cacheable注解很方便的使用redis缓存,但是对于缓存的过期时间配置,需要针对cacheNames单独额外配置,有点麻烦。于是我就想有没有什么办法可以加以改造使之更加方便?
考虑到同一模块的业务一般使用同一个cacheName,且同一个cacheName的过期时间一般不变。那么能不能在cacheName上进行一些改造呢?于是我分析了下源码,从RedisCacheManager这个类开始着手,通过源码我们发现,在RedisCacheManager中有这么一个方法:
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
通过名字以及返回值我们可以猜测,我们所使用的的缓存都是一个个由该方法创建出来的RedisCache对象管理。通过对该方法进行Find Usages分析,我们发现有2个地方使用到这个方法:
/*
* (non-Javadoc)
* @see org.springframework.cache.support.AbstractCacheManager#loadCaches()
*/
@Override
protected Collection<RedisCache> loadCaches() {
List<RedisCache> caches = new LinkedList<>();
for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
caches.add(createRedisCache(entry.getKey(), entry.getValue()));
}
return caches;
}
/*
* (non-Javadoc)
* @see org.springframework.cache.support.AbstractCacheManager#getMissingCache(java.lang.String)
*/
@Override
protected RedisCache getMissingCache(String name) {
return allowInFlightCacheCreation ? createRedisCache(name, defaultCacheConfig) : null;
}
其中loadCaches()猜测是如果有初始化配置时,用来初始化已经配置的缓存;getMissingCache()猜测是在使用过程中,如果根据name获取不到缓存对象则进行创建(allowInFlightCacheCreation需要为true才有效,该值默认是true)。我们在AbstractCacheManager这个抽象类中的这两个方法可以证实我们的猜测没错:
public void initializeCaches() {
Collection<? extends Cache> caches = this.loadCaches();
synchronized(this.cacheMap) {
this.cacheNames = Collections.emptySet();
this.cacheMap.clear();
Set<String> cacheNames = new LinkedHashSet(caches.size());
Iterator var4 = caches.iterator();
while(var4.hasNext()) {
Cache cache = (Cache)var4.next();
String name = cache.getName();
this.cacheMap.put(name, this.decorateCache(cache));
cacheNames.add(name);
}
this.cacheNames = Collections.unmodifiableSet(cacheNames);
}
}
@Nullable
public Cache getCache(String name) {
Cache cache = (Cache)this.cacheMap.get(name);
if (cache != null) {
return cache;
} else {
synchronized(this.cacheMap) {
cache = (Cache)this.cacheMap.get(name);
if (cache == null) {
cache = this.getMissingCache(name);
if (cache != null) {
cache = this.decorateCache(cache);
this.cacheMap.put(name, cache);
this.updateCacheNames(name);
}
}
return cache;
}
}
}
根据上面分析,createRedisCache()是最终创建缓存对象的地方,而且该方法入参有:缓存名,缓存配置。也刚好符合我们的需要,所以我们在这里进行改造最合适不过。
我们可以创建一个自定义的RedisCacheManager命名为CustomTtlRedisCacheManager并且继承RedisCacheManager,然后在新类中对createRedisCache进行重载改造。具体参见代码:
/**
* 通过cacheName自定义过期时间的RedisCacheManager
* 支持直接使用cacheName来定义过期时间
* cacheName:name#time time为过期时间,单位秒,0为不过期
* 例如:test#100 意思是定义一个名为test#100的缓存,且过期时间为100秒
*/
public class CustomTtlRedisCacheManager extends RedisCacheManager {
public CustomTtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
public CustomTtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
super(cacheWriter, defaultCacheConfiguration, initialCacheNames);
}
public CustomTtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);
}
public CustomTtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
}
public CustomTtlRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
Duration ttl = getTtlByName(name);
if (ttl != null) {
//证明在cacheName上使用了过期时间,需要修改配置中的ttl
cacheConfig = cacheConfig.entryTtl(ttl);
}
//修改缓存key和value值的序列化方式
cacheConfig = cacheConfig.computePrefixWith(DEFAULT_CACHE_KEY_PREFIX)
.serializeValuesWith(DEFAULT_PAIR);
return super.createRedisCache(name, cacheConfig);
}
/**
* 缓存参数的分隔符
* 数组元素0=缓存的名称
* 数组元素1=缓存过期时间TTL
*/
private static final String DEFAULT_SEPARATOR = "#";
/**
* 通过name获取过期时间
* @param name
* @return
*/
private Duration getTtlByName(String name) {
if (name == null) {
return null;
}
//根据分隔符拆分字符串,并进行过期时间ttl的解析
String[] cacheParams = name.split(DEFAULT_SEPARATOR);
if (cacheParams.length > 1) {
String ttl = cacheParams[1];
if (!StringUtils.isEmpty(ttl)) {
try {
return Duration.ofSeconds(Long.parseLong(ttl));
} catch (Exception e) {
}
}
}
return null;
}
/**
* 默认的key前缀
*/
private static final CacheKeyPrefix DEFAULT_CACHE_KEY_PREFIX = new CacheKeyPrefix() {
@Override
public String compute(String cacheName) {
return cacheName+":";
}
};
/**
* 默认序列化方式为json
*/
private static final RedisSerializationContext.SerializationPair<Object> DEFAULT_PAIR = RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer());
}
注释写的挺详细,具体逻辑参考代码及注释。因为我们自定义了一个自己的RedisCacheManager类。所以我这里顺便把上文中对缓存key和value序列化的修改也集成了,这样的话我们RedisConfig类中需要修改为使用我们自定义的RedisCacheManager,代码如下:
@Configuration
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//初始化一个RedisCacheConfiguration
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//返回一个自定义的CacheManager
return new CustomTtlRedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
}
到此我们的改造就完成了,现在我们可以轻松的通过cacheName来对缓存的过期时间进行设置。如:
@Cacheable(cacheNames = "testCache#5", key = "'testKey'")
表示缓存名为testCache#5,且缓存在5秒之后自动过期。如果没有带#号,或者#号后面的不是合法的数字,则过期时间不生效,跟原版的RedisCacheManager的配置一样。
代码:https://gitee.com/lqccan/blog-demo/tree/master/SpringBoot/redis-custom-ttl
相关链接:SpringBoot之RedisTemplate操作redis出现\xAC\xED\x00\x05t\x00\x08乱码问题