参考文章:https://medium.com/@jerome.waibel/caching-with-spring-boot-and-redis-can-be-tricky-5f99548601b9
SpringBoot中内置有一个ObjectMapper的Bean,一般而言我们对于Json序列化的规则有2种,
- 一种是接口的参数已经返回的结果的序列化 这种情况下 SpringBoot已经知道需要参与序列化的具体类型的信息,所以只需要得到json字符串的内容就可以进行序列化/反序列化 因此采用默认的ObjectMapper就可以了
- 另外一种是存储到数据库的序列化,这种序列化规则往往与Redis或者Mongo等缓存组件有关系,除了采用
GenericJackson2JsonRedisSerializer
这个序列化器之外 比较通用还有一个Jackson2JsonRedisSerializer
,Jackson2JsonRedisSerializer对于对于泛型对象序列化的时候需要记录类的信息在json序列化的值里面 可以用附加一个@class属性的字段来进行标注 一般在RedisTemplate<String,Object> 泛型类序列化的时候比较常用
当我想全局采用上面那种ObjectMapper的情况下,就会出现使用泛型RedisTemplate<String,Object>进行序列化的时候,出现强转类型的错误,因为Redis当中没有记录类的类名信息,因此默认策略会将其保存为一个LinkedHashMap,那么就会出现如下的错误 例如
class java.util.LinkedHashMap cannot be cast to class com.domain.dto.UserOnlineDto (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; domain.dto.UserOnlineDto is in unnamed module of loader org.springframework.boot.loader.launch.LaunchedClassLoader @41629346)
那么如果全局采用下面那种情况,就会出现 Http请求的接口会把返回的类的类名信息也带上,因此需要分开构造这两种ObjectMapper,切记不能全局注册一个Bean 作为一个ObjectMapper
LocalDateTime反序列化问题
另外现在在操作时间的时候比较推荐采用java.time.LocalDateTime 等一系列的类,但是Jackson默认没有提供序列化器,因此我们需要在项目中引入第三方的依赖包如下
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.0</version>
</dependency>
然后我们只需要在SpringBoot环境中注册一个javaTime模块,那么SpringBoot默认的ObjectMapper就会自动添加这个模块类(JavaTimeModule)
Spring Boot 将自动注册任何类型为com.fasterxml.jackson.databind.Module的 bean
刚才引入的依赖中包含了这个模块因此我们只需要提供这个Bean即可,这个是比较SpringBoot的Boot味道的做法,但是不了解Bean加载机制的话有点摸不着头脑
简单来说按照如下注册时间模块就可以了
@Bean
public JavaTimeModule javaTimeModule(){
JavaTimeModule javaTimeModule = new JavaTimeModule();
DateTimeFormatter patter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(patter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(patter));
return javaTimeModule;
}
RedisTemplate<String,Object> 泛型类的配置参考
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper)
{
ObjectMapper om = objectMapper.copy();
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Object.class)
.build();
om.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置value值的序列化
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(om, Object.class);
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashValueSerializer(serializer);
// key hasKey的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
在这段代码中 我们首先copy了默认注入的全局的ObjectMapper后面简称om,然后给这个om设置如下策略
ObjectMapper om = objectMapper.copy();
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Object.class)
.build();
om.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
这表示把json的类型信息作为属性写入到序列化的值里面去,这样就可以比较方便的拿到Redis当中的序列化类的信息了