菜单

Jackson中序列化器的设置

duckflew
发布于 2024-08-27 / 29 阅读
0
0

Jackson中序列化器的设置

参考文章: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当中的序列化类的信息了


评论