SpringBoot中的序列化与反序列化:Redis配置

序列化与反序列化都是以网络传输为应用前提的。

  • jdk提供序列化接口,是个标记接口;
  • jdk提供序列化方法(输入输出流方法);
  • springboot对声明的实体类实现序列化接口;
  • springboot提供自定义的序列化接口,也称为消息转换器;
  • 有第三方库也提供各种MessageConvertor接口,可在MvcConfig中进行配置。

1. 序列化&反序列化

网络传输过程中,通常以字节流形式传输数据,而java-web亦是如此:

Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程。对于基本数据类型,可以很方便地转为字节数组,而对于对象类型,则需要通过序列化器来做。被序列化的对象需要实现java.io.Serializable接口,该接口只是一个标记接口,不用实现任何方法。

JDK提供了Java对象的序列化方式实现对象序列化传输,主要通过输出流java.io.ObjectOutputStream和对象输入流java.io.ObjectInputStream来实现:

  • java.io.ObjectOutputStream:表示对象输出流 , 它的writeObject(Object obj)方法可以对参 数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中

  • java.io.ObjectInputStream:表示对象输入流 ,它的readObject()方法源输入流中读取字节序 列,再把它们反序列化成为一个对象,并将其返回

serialVersionUID

这是一个重要的标识,只有序列化对象的~id和这个id一致时,才能反序列化为原对象,所以一般是自己设定一个固定值,如:public static final Long serialVersionUID = 1L。
SpringBoot中的序列化&反序列化
参考:springboot:序列化与反序列化

image-20221205115311117

基本序列化/反序列化

Json是一种轻量级的文本数据交换格式,在Json字符串中{}用来表示对象,[]用来表示列表,数据以key-value的形式存放:

{
  "name":"zhangsan",
  "age":"22",
  "course":["java","python"]
}

在 Spring Boot中,想要一个接口接收Json格式的数据并返回Json格式的数据,前端将http请求头Accept设置为“application/json”,Content-Type"application/json"
中间件只需要在Controller类中做如下定义:

@RestController
@RequestMapping("/example")
public class ExampleController {
    @Aurowired
    private SomeService someService;
    
    @RequestMapping("/getSth")
    public Result getBaseInfo(@RequestBody HashMap<String,Object> map){
        return someService.get(map);
    }
}
  • @ResponseBody
    在 Controller 中使用@ResponseBody注解即可返回 Json 格式的数据,而@RestController注解包含了@ResponseBody 注解,所以默认情况下,@RestController即可将返回的数据结构转换成Json格式。
  • @RequestBody
    相应地,@RequestBody也将接收到的json数据转为相应类型。
    而这些注解之所以可以进行Json与JavaBean之间的相互转换,就是因为HttpMessageConverter发挥着作用。
  • HttpMessageConverter
    org.springframework.http.converter.HttpMessageConverter是一个策略接口,是Http request请求和response响应的转换器。Spring为其提供了多个实现类,在项目启动时也会自动加载部分内置的序列化策略(有优先级)。
  • 自定义序列化器
    可以通过配置第三方反/序列化器来定义化一些需求,如LocalDataTime格式的数据转为String、及其反序列化等。
@Override // 注意这里是继承的方法,亦即本来就有部分序列化器被加载到
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    // 创建转换器
    MappingJackson2HttpMessageConverter converter = 
        new MappingJackson2HttpMessageConverter();
    // 设置自定义对象转换器
    converter.setObjectMapper(objectMapper); // 这里的objectMapper就是第三方xx了
    // 把转换器放入list中:index设置优先级
    converters.add(0, converter);
}
@Component
public class JacksonObjectMapper extends ObjectMapper {

    public static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
    public static final String DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_TIME_FORMAT = "HH:mm:ss";

    public JacksonObjectMapper() {
        super();
        //收到未知属性时不报异常
        this.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);

        //反序列化时,属性不存在的兼容处理
        this.getDeserializationConfig().withoutFeatures(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);


        SimpleModule simpleModule = new SimpleModule()
                .addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)))

                .addSerializer(BigInteger.class, ToStringSerializer.instance)
                .addSerializer(Long.class, ToStringSerializer.instance)
                .addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)))
                .addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)))
                .addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        //注册功能模块 例如,可以添加自定义序列化器和反序列化器
        this.registerModule(simpleModule);
    }
}

2. redis的序列化

——这里的序列化和web序列化不是一个东西
Redis缓存的两种方式:自定义、结合SpringCache:分别对应RedisTemplateCacheManager(一般使用RedisCacheManager实现类)。

下为RedisTemplateRedisCacheManager这里不说了:

redis的数据分为key和value(key, hashkey, value, hashvalue),针对key一般都是String类型的序列化方式,而value则一般使用对象2json的序列化方式。

spring为redis提供以下几种序列化实现:

image-20221205115311117
  • Jdk~:默认序列化方式,一般不用它
  • String~:String类型序列化方式,底层就是toString
  • GenericJackson2~:对象转json序列化方式,将java对象转json类型字符串
  • Jackson2Json~:对象转json序列化方式,可自定义ObjectMapper类型属性定制序列化规则

在配置RedisTemplate对应的Bean时设置以上序列化器即可。

@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 这个connectionFactory的Bean是哪里定义的:boot帮忙创建的
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        
        // key序列化:String~
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);

        // value序列化:obj2json
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);

        // return Bean
        return redisTemplate;
    }
}

3. LocalDataTime类型序列化规则

当对象中有时间信息,即LocalDataTime类型数据时,redis的序列化器会将这个数据拆开(年、月、日等),之后反序列化取对象时就会出错,所以设置其对应的序列化规则(也可以手动转换,即手动转localDT为String,用的时候再转回来),这里应该使用Jackson2Json~

@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        // 这个connectionFactory的Bean是哪里定义的:boot帮忙创建的
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // 默认的Key/Field序列化器为:JdkSerializationRedisSerializer
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);

        // 使用Jackson2json而非GenericJackson2json:主要为解决LocalDataTime序列化问题
        Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        // 配置localDataTime序列化
        jsonRedisSerializer.setObjectMapper(getLocalDataTimeMapper());
        // 配置value序列化
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);

        return redisTemplate;
    }

    private ObjectMapper getLocalDataTimeMapper() {
        // 这里定义了OM中对于LocalDataTime的序列化规则
        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        //LocalDatetime序列化
        JavaTimeModule timeModule = new JavaTimeModule();

        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalDate.class,
                new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));

        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDateTime.class,
                new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        // 注册localDataTime序列化
        objectMapper.registerModule(timeModule);

        return objectMapper;
    }
}

4. 参考

Redis两种配置方式

redis序列化java8 LocalDateTime

posted @ 2023-02-01 15:35  YIYUYI  阅读(3329)  评论(0编辑  收藏  举报