spring boot 2.0的cache config变化很大, 普遍使用了build模式.
一个比较简单的配置:
@Bean(name = "cacheManager")
@Primary
public CacheManager cacheManager(ObjectMapper objectMapper, RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(CacheTime.DEFAULT))
.disableCachingNullValues()
.computePrefixWith(cacheName -> "yourAppName".concat(":").concat(cacheName).concat(":"))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(createJackson2JsonRedisSerializer(objectMapper)));
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
代码说明:
1. entryTtl: 定义默认的cache time-to-live.
2. disableCachingNullValues: 禁止缓存Null对象. 这个识需求而定.
3. computePrefixWith: 此处定义了cache key的前缀, 避免公司不同项目之间的key名称冲突.
4. serializeKeysWith, serializeValuesWith: 定义key和value的序列化协议, 同时的hash key和hash value也被定义.
自定义Cache key 的生成策略
在上述配置中, 生成的key是默认的SimpleKey对象, 说实在的并不好用, 容易造成redis key过长的问题. 此处, 我们自定义key的生成策略, 将方法参数转换为hashcode, 作为redis key. 需要做两个事情, 一个是添加一个自定义的ConversionService, 另一个是需要自定义一个KeyGenerator.
@Bean(name = "cacheManager")
@Primary
public CacheManager cacheManager(ObjectMapper objectMapper, RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(CacheTime.DEFAULT))
.disableCachingNullValues()
.computePrefixWith(cacheName -> "yourAppName".concat(":").concat(cacheName).concat(":"))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(createJackson2JsonRedisSerializer(objectMapper)))
.withConversionService(new CacheKeyConversionService());
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
@Bean(name = "cacheKeyGenerator")
@Primary
public KeyGenerator keyGenerator() {
return (target, method, params) -> CacheHashCode.of(params);
}
注意到withConversionService(new CacheKeyConversionService())这句注册了一个ConversionService, 作用于Cache对象. 而KeyGenerator会作用@Cacheable注解的方法, 将方法的参数转换为hashCode. 这样的话, Cache对象和@Cacheable就打通了, 可以实现互操作. 即, 可以通过直接操纵Cache对象来读取@Cacheable方法的缓存数据. 这在实际项目中是非常有用的. 但这样做的代价是缓存容易被开发人员滥用, 做架构者不得不考虑这一点. 我也是在开发人员的强烈要求下才加入的, 后续打算通过一个配置默认关闭Cache和@Cacheable之间的互通.
补上其他的代码:
/**
* Convert cache key to hash code.
*/
class CacheKeyConversionService implements ConversionService {
@Override
public boolean canConvert(@Nullable Class> sourceType, Class> targetType) {
return true;
}
@Override
public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
return true;
}
@Nullable
@Override
public T convert(@Nullable Object source, Class targetType) {
return (T) convert(source);
}
@Nullable
@Override
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
return convert(source);
}
private Object convert(Object source) {
if (source instanceof CacheHashCode) {
return ((CacheHashCode) source).hashString();
}
return CacheHashCode.of(source).hashString();
}
}
/**
* Hash code generator.
*/
static class CacheHashCode {
private Object[] params;
private int code;
private CacheHashCode(Object[] params) {
this.params = params;
this.code = Arrays.deepHashCode(params);
}
public static CacheHashCode of(Object object) {
return new CacheHashCode(ArrayUtil.isArray(object) ? ArrayUtil.toObjectArray(object) : new Object[]{object});
}
@Override
public int hashCode() {
return code;
}
public String hashString() {
return code + "";
}
@Override
public String toString() {
return "CacheHashCode{" +
"params=" + Arrays.toString(params) +
", code=" + code +
'}';
}
}