通过对value的值的约定分割实现动态设置过期时间
@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {
@Value("${spring.cache.entryTtl:3600}")
private Long entryTtl;
private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
private static final RedisSerializer JSON_SERIALIZER = new GenericFastJsonRedisSerializer();
private static final RedisSerializationContext.SerializationPair<String> STRING_PAIR =
RedisSerializationContext.SerializationPair.fromSerializer(STRING_SERIALIZER);
private static final RedisSerializationContext.SerializationPair<Object> JSON_PAIR =
RedisSerializationContext.SerializationPair.fromSerializer(JSON_SERIALIZER);
private final RedisConnectionFactory redisConnectionFactory;
public RedisCacheConfig(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// set key serializer
// 设置key序列化类,否则key前面会多了一些乱码
template.setKeySerializer(STRING_SERIALIZER);
template.setHashKeySerializer(STRING_SERIALIZER);
template.setValueSerializer(JSON_SERIALIZER);
template.setHashValueSerializer(JSON_SERIALIZER);
// 如果 KeySerializer 或者 ValueSerializer 没有配置,则对应的 KeySerializer、ValueSerializer 才使用这个 Serializer
template.setDefaultSerializer(JSON_SERIALIZER);
// factory
template.setConnectionFactory(redisConnectionFactory);
template.afterPropertiesSet();
return template;
}
@Override
public CacheErrorHandler errorHandler() {
return new RedisCacheErrorHandler();
}
@Bean
@Override
public KeyGenerator keyGenerator() {
return (o, method, objects) -> {
StringBuilder sb = new StringBuilder(32);
sb.append(o.getClass().getSimpleName());
sb.append(".");
sb.append(method.getName());
if (objects.length > 0) {
sb.append("#");
}
String sp = "";
for (Object object : objects) {
sb.append(sp);
if (object == null) {
sb.append("NULL");
} else {
sb.append(object.toString());
}
sp = ".";
}
return sb.toString();
};
}
@Bean
@Override
public CacheManager cacheManager() {
// 初始化一个RedisCacheWriter
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
// 设置默认过期时间:1 分钟
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()
.entryTtl(Duration.ofSeconds(entryTtl))
// 使用注解时的序列化、反序列化
.serializeKeysWith(STRING_PAIR).serializeValuesWith(JSON_PAIR);
//返回自定义CacheManager
return new CusRedisCacheManager(cacheWriter, defaultCacheConfig);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(STRING_SERIALIZER);
redisTemplate.setValueSerializer(STRING_SERIALIZER);
return redisTemplate;
}
}
//实现
public class CusRedisCacheManager extends RedisCacheManager {
private final RedisCacheWriter cacheWriter;
private final RedisCacheConfiguration defaultCacheConfig;
/**
* 用于返回自定义的redisCache
**/
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
return new CusRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
}
public CusRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
this.cacheWriter = cacheWriter;
this.defaultCacheConfig = defaultCacheConfiguration;
}
}
@Slf4j
public class CusRedisCache extends RedisCache {
private RedisCacheWriter redisCacheWriter;
private RedisCacheConfiguration configuration;
/**
* 校验规则:获取时间
*/
private final static String REGEX_STR = ".*\\#\\d+$";
private static final String SPLITTER = "#";
/**
* Create new {@link RedisCache}.
*
* @param name must not be {@literal null}.
* @param cacheWriter must not be {@literal null}.
* @param cacheConfig must not be {@literal null}.
*/
protected CusRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
super(name, cacheWriter, cacheConfig);
redisCacheWriter = cacheWriter;
configuration = cacheConfig;
}
@Override
public synchronized <T> T get(Object key, Callable<T> valueLoader) {
if (key == null) {
log.error("redis cache key can not be null");
return null;
}
return super.get(key, valueLoader);
}
/**
* 重写cache put 逻辑,引入自定义TTL 实现
* 实现逻辑:
* 1.通过获取@Cacheable 中的value ,然后根据约定好的特殊字符进行分割
* 2.从分割结果集中获取设置的TTL 时间并将value 中的,然后给当前缓存设置TTL
*
* @param key
* @param value
*/
@Override
public void put(Object key, Object value) {
String name = super.getName();
//是否按照指定的格式
if (Pattern.matches(REGEX_STR, name)) {
putWithExpireTime(name, key, value);
} else {
//原来逻辑处理
super.put(key, value);
}
}
public void putWithExpireTime(String name, Object key, @Nullable Object value) {
List<String> keyList = Arrays.asList(name.split(SPLITTER));
//获取键值
String finalName = keyList.get(0);
//获取TTL 执行时间
Long ttl = Long.valueOf(keyList.get(1));
//获取缓存value
Object cacheValue = preProcessCacheValue(value);
//获取value 为null 时,抛出异常
if (!isAllowNullValues() && cacheValue == null) {
log.error(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));
return;
}
//插入时添加时间
redisCacheWriter.put(finalName, serializeCacheKey(createCacheKey(key)), serializeCacheValue(cacheValue),
Duration.ofSeconds(ttl));
}
/**
* @描述 现有key 值格式为 key#ttl ;改方法将key 值后边的#ttl 去掉 ;例如test# 10;改方法处理后为test
*/
@Override
protected String createCacheKey(Object key) {
String convertedKey = convertKey(key);
if (!configuration.usePrefix()) {
return convertedKey;
}
return prefixCacheKey(convertedKey);
}
private String prefixCacheKey(String key) {
String name = super.getName();
if (Pattern.matches(REGEX_STR, name)) {
List<String> keyList = Arrays.asList(name.split(SPLITTER));
String finalName = keyList.get(0);
return configuration.getKeyPrefixFor(finalName) + key;
}
// allow contextual cache names by computing the key prefix on every call.
return configuration.getKeyPrefixFor(name) + key;
}
}