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 publicT 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 + '}'; } }