springboot下用cache注解整合redis並使用json序列化反序列化。
cache注解整合redis
最近發現spring的注解用起來真的是很方便。隨即產生了能不能吧spring注解使用redis實現的方式。
只需要在配置文件中(application.propertoes)添加如下一個配置
spring.cache.type=redis
並配置好redis的相關信息
spring.redis.database=0
spring.redis.host=
spring.redis.port=
spring.redis.password=
spring.redis.timeout=5000ms
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
springcache注解整合redis非常容易就整合完成了。
redis緩存序列化與反序列化
由於緩存數據使用的是jdk自帶的序列化 需要序列化的實體類繼承Serializable接口。而且序列化后的內容在redis中看起來也不是很方便。
於是萌生了需要將數據序列化成json的想法。
經過一番研究后決定寫一個redis 配置文件。RedisConfig具體內容如下
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofDays(30));
return configuration;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
//初始化一個RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
//設置CacheManager的值序列化方式為 fastJsonRedisSerializer,但其實RedisCacheConfiguration默認使用StringRedisSerializer序列化key,
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer);
RedisCacheConfiguration defaultCacheConfig=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair);
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
}
配置完成后,數據訪問序列化都非常正常,redis中也可以看到有序的json數據。
{
"name": "long",
"age": 18,
"height": 1.72
}
但是。。。。
當再次訪問時出現了一個奇怪的問題。
LinkedHashMap 不能轉換為實體類。
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.alance.springcachedemo.entity.User
這看起來很像泛型丟失啊。
隨即第三版配置文件出爐了,配置一下序列化的泛型保存
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
MyObjectMapper objectMapper = new MyObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
MyObjectMapper objectMapper = new MyObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
}
private class MyObjectMapper extends ObjectMapper {
private static final long serialVersionUID = 1L;
public MyObjectMapper() {
super();
// 去掉各種@JsonSerialize注解的解析
this.configure(MapperFeature.USE_ANNOTATIONS, false);
// 只針對非空的值進行序列化
this.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 將類型序列化到屬性json字符串中
this.enableDefaultTyping(DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
// 對於找不到匹配屬性的時候忽略報錯
this.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 不包含任何屬性的bean也不報錯
this.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
}
}
}
好了這下反序列化的問題解決了,
redis中
[
"com.alance.springcachedemo.entity.User",
{
"name": "long",
"age": 18,
"height": 1.72
}
]
redis存儲的json中帶上了類型
至此這個問題就解決了。
后記
在愉快使用緩存注解的時候,發現緩存注解並 不能和諸如事務注解線程池注解一起使用。這是aop代理的特性決定的。而且 方法的類內部調用也不走注解。
enableDefaultTyping 這個功能涉及到java著名的反序列化漏洞。各位系統之間調用數據的項目還是慎重使用.
漏洞詳情
后記的后記
在寫代碼的過程中發現了一個更優雅的解決方案分享給大家。
只需要在配置文件中配置一下CacheManger,使用jackson的一個帶泛型的序列化工具實現。
/**
* spring cache 注解相關序列化操作
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 配置序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheConfiguration redisCacheConfiguration = config
// 鍵序列化方式 redis字符串序列化
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
// 值序列化方式 簡單json序列化
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer));
return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
}