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

起因:一次需求对一个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序列化思考)