在使用Spring cache以注解+AOP的方式封装了操作的缓存的统一处理。注解Cacheable,先查询缓存,如果没有值,执行方法把返回结果保存到缓存。CachePut执行方法,把返回的结果保存的缓存。CacheEvict删除缓存。在设置缓存的时候,提供了默认的失效时间的参数配置,但是没有提供用户自定义失效时间的设置,该功能需要用户自定义实现。以redis缓存自定义失效时间为例,讲解下实现方案。
在设置CacheManager时,有设置默认的失效时间属性,代码如下:
@Bean
@ConditionalOnMissingBean(value = CacheManager.class)
public CacheManager cacheManager(@Autowired RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置缓存的默认过期时间
config = config.entryTtl(Duration.ofHours(1))
// 不缓存空值
.disableCachingNullValues();
RedisCacheManager cacheManager = new ExpireTimeRedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), config);
return cacheManager;
}
在设置缓存的注解,Cacheable和CachePut都没有缓存失效时间的属性。如果实现自定义key失效时间需要用户自定义。分析源码AbstractCacheManager得知,在从缓存获取数据的时,调用方法Cache getCache(String name),如果缓存中没有数据,则调用Cache getMissingCache(String name)方法获取缓存数据。代码如下:
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
......
@Override
@Nullable
public Cache getCache(String name) {
// Quick check for existing cache...
Cache cache = this.cacheMap.get(name);
if (cache != null) {
return cache;
}
// The provider may support on-demand cache creation...
Cache missingCache = getMissingCache(name);
if (missingCache != null) {
// Fully synchronize now for missing cache registration
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = decorateCache(missingCache);
this.cacheMap.put(name, cache);
updateCacheNames(name);
}
}
}
return cache;
}
......
/**子类实现
*/
@Nullable
protected Cache getMissingCache(String name) {
return null;
}
}
在源码中RedisCacheManager,实现方法getMissingCache的逻辑可知,生成缓存由方法edisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig)实现。代码如下:
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {
......
@Override
protected RedisCache getMissingCache(String name) {
return allowInFlightCacheCreation ? createRedisCache(name, defaultCacheConfig) : null;
}
......
protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
......
}
通过源码分析得知,实现自定义失效时间需要在执行createRedisCache方法时,重新设置失效时间配置即可。我实现的方案是在Cacheable和CachePut设置缓存名称时,设置自定义时间的配置,缓存名称的示例值为users#10_SECONDS,缓存名称#失效时间值_失效时间单位,#为缓存名称与失效时间配置的分隔符;_为失效时间与失效时间单位的分隔符。代码如下:
@Configuration
@Slf4j
@EnableCaching
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration extends CachingConfigurerSupport {
/**
* redis FastJson序列化
*/
@Bean
@ConditionalOnMissingBean(value = RedisSerializer.class)
public RedisSerializer<Object> redisSerializer() {
return new GenericFastJsonRedisSerializer();
}
/**
* 设置自定义序列化的RedisTemplate
*
* @param redisConnectionFactory
* @param redisSerializer 序列化
* @return
*/
@Bean
@ConditionalOnMissingBean(RedisTemplate.class)
public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory,
@Autowired RedisSerializer<Object> redisSerializer) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// value,hash value设置FastJson序列化
template.setHashValueSerializer(redisSerializer);
template.setValueSerializer(redisSerializer);
// key,hash key使用String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(Charset.forName("UTF-8"));
template.setHashKeySerializer(stringRedisSerializer);
template.setKeySerializer(stringRedisSerializer);
return template;
}
/**
* 配置 spring cache manager RedisCacheManager
*/
@Bean
@ConditionalOnMissingBean(value = CacheManager.class)
public CacheManager cacheManager(@Autowired RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置缓存的默认过期时间
config = config.entryTtl(Duration.ofHours(1))
// 不缓存空值
.disableCachingNullValues();
RedisCacheManager cacheManager = new ExpireTimeRedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), config);
return cacheManager;
}
/**
* 可以自动设置失效时间的RedisCacheManager
* 失效时间设置规则 @Cacheable(cacheNames = "users#10_SECONDS")
* # 为cacheNames 与自定义失效时间的分隔符
* _ 为失效时间值与单位的分隔符
*
* @Author iloveoverfly
* @Date 2020/2/13 19:26
**/
@Slf4j
private static class ExpireTimeRedisCacheManager extends RedisCacheManager {
private ExpireTimeRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
}
/**
* 生成缓存配置
*
* @param name 设置的缓存名称(完整配置的例子users#10_SECONDS)
* @param cacheConfig redis缓存配置
* @return
*/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
if (StringUtils.isBlank(name)) {
return super.createRedisCache(name, cacheConfig);
}
String[] nameSplits = name.split(NAME_TIME_CONFIG_SEPARATOR);
if (ArrayUtils.isNotEmpty(nameSplits) && 1 == nameSplits.length) {
if (log.isDebugEnabled()) {
log.debug("set default ttl millis {}", cacheConfig.getTtl().toMillis());
}
return super.createRedisCache(name, cacheConfig);
}
String finalName = nameSplits[0];
String ttlTimeConfig = nameSplits[1];
String[] ttlTimeConfigSplits = ttlTimeConfig.split(TTL_TIME_SEPARATOR);
if (ArrayUtils.isEmpty(ttlTimeConfigSplits) || 2 != ttlTimeConfigSplits.length) {
log.info("ttl time config {} is invalid!", ttlTimeConfig);
return super.createRedisCache(finalName, cacheConfig);
}
try {
Long time = Long.parseLong(ttlTimeConfigSplits[0]);
String timeUnitName = ttlTimeConfigSplits[1].toUpperCase();
TimeUnit timeUnit = TimeUnit.valueOf(timeUnitName);
Duration ttl = obtainAndAddRandomSecs(time, timeUnit);
RedisCacheConfiguration newTtlRedisCacheConfiguration = cacheConfig.entryTtl(ttl);
return super.createRedisCache(finalName, newTtlRedisCacheConfiguration);
} catch (Exception e) {
log.warn("obtain ttl time config error!", e);
}
return super.createRedisCache(finalName, cacheConfig);
}
/**
* 获取ttl失效时间并且随机增加毫秒数
*
* @param time
* @param timeUnit
* @return
*/
private Duration obtainAndAddRandomSecs(Long time, TimeUnit timeUnit) {
long seconds;
switch (timeUnit) {
case SECONDS:
seconds = time;
break;
case MILLISECONDS:
seconds = TimeUnit.MILLISECONDS.toSeconds(time);
break;
case HOURS:
seconds = TimeUnit.HOURS.toSeconds(time);
break;
case MINUTES:
seconds = TimeUnit.MINUTES.toSeconds(time);
break;
case DAYS:
seconds = TimeUnit.DAYS.toSeconds(time);
break;
default:
seconds = TimeUnit.SECONDS.toSeconds(time);
break;
}
// 随机增加失效秒数,避免同一时间集体失效
long secondsWithRandom = seconds + RedisConst.EXPIRE_RANDOM.nextInt(10);
return Duration.ofSeconds(secondsWithRandom);
}
}
}