美文网首页
一次生产事故引发的JDK序列化思考

一次生产事故引发的JDK序列化思考

作者: 小胖学编程 | 来源:发表于2020-11-27 16:00 被阅读0次

    起因:一次需求对一个pojo类增加了新字段,并保存到了Redis中,测试没有问题,但是到生产环境却出现了反序列化失败。

    1. 问题起因以及解决方案

    1. 失败原因:

    "Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException:
     Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is 
    java.io.InvalidClassException: com.tellme.controller.RedisController$User; local class incompatible: 
    stream classdesc serialVersionUID = 3688184446225449117,
     local class serialVersionUID = 6846178843376749314"
    

    复习盘点-Java序列化方式(2)JAVA原生序列化以及Protostuff序列化中,指出反序列化不同的UID导致InvalidClassException异常。

    原始代码:

    @RestController
    public class RedisController {
    
    
        @Autowired
        private RedisTemplate redisTemplate;
        
        //某一接口存储到Redis中
        @GetMapping("redis/set")
        public String set() {
            User user=new User();
            user.setId(100);
            user.setName("tom");
            //保存到Redis中
            redisTemplate.opsForValue().set("b:test",user);
            User o = (User)redisTemplate.opsForValue().get("b:test");
            return o.toString();
        }
        //别的接口在获取Redis里面存储的对象
        @GetMapping("redis/get")
        public String get() {
            User o = (User)redisTemplate.opsForValue().get("b:test");
            return o.toString();
        }
    
        @Data
        @ToString
        public static class User implements Serializable {
            private int id;
            private String name;
        }
    
    }
    

    注:Redis使用了默认的JDK的序列化方式和反序列化方式。

    而一次新的需求中,User对象加了一个字段,导致旧数据反序列化失败

    根本原因:serialVersionUID是自动生成的,类被修改后,生成的serialVersionUID就会变化。旧数据使用上一个版本的serialVersionUID存储到Redis中,而本地类的版本号为新的serialVersionUID。故不能反序列化。

    那出现这个问题,解决方案是什么呢?类需要手动声明修改前的serialVersionUID

    @RestController
    public class RedisController {
        @Autowired
        private RedisTemplate redisTemplate;
    
        @GetMapping("redis/set")
        public String set() {
            User user=new User();
            user.setId(100);
            user.setName("tom");
            //保存到Redis中
            redisTemplate.opsForValue().set("b:test",user);
            User o = (User)redisTemplate.opsForValue().get("b:test");
            return o.toString();
        }
        @GetMapping("redis/get")
        public String get() {
    
            User o = (User)redisTemplate.opsForValue().get("b:test");
            return o.toString();
        }
    
        @Data
        @ToString
        public static class User implements Serializable {
            //手动声明为旧版本的序列化号
            private static final long serialVersionUID = 3688184446225449117L;
            private int id;
            private String name;
            //新加的字段
            private int age;
        }
    
    }
    

    2. 思考

    使用JDK序列化时,pojo类必须声明Serializable接口,pojo类中推荐手动声明serialVersionUID,设置为1L即可。

    image.png

    推荐阅读

    复习盘点-Java序列化方式(2)JAVA原生序列化以及Protostuff序列化

    相关文章

      网友评论

          本文标题:一次生产事故引发的JDK序列化思考

          本文链接:https://www.haomeiwen.com/subject/rqvxwktx.html