在上一篇文章:SpringBoot中redis缓存的配置及使用 中简单介绍了了springboot 2.x中redis缓存的配置及使用,并且修改了默认的序列化方式,修改value值的序列化方式为json。可以直接使用注解方便的使用缓存。但是我觉得还不够,因为我在使用中有涉及到缓存时间的设置,如果针对一个个缓存单独去设置的话太过麻烦。
于是我就有没有什么办法可以加以改造使之更加方便?考虑到统一模块的业务一般使用同一个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 = "test_cache#600", key = "#root.target.KEY")
表示缓存名为test_cache#600,且缓存在600秒之后自动过期。如果没有带#号,或者#号后面的不是合法的数字,则过期时间不生效,跟原版的RedisCacheManager的配置一样。
具体完整的代码见:https://gitee.com/lqccan/blog-demo demo8