开源一夏 | 一场由 serialVersionUID 引发的线上问题
既然提到了 serialVersionUID,那么首先需要了解什么是 serialVersionUID?
什么是 serialVersionUID
简单来说,Java 的序列化机制是通过在运行时判断类的 serialVersionUID 来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体(类)的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
当实现 java.io.Serializable 接口的实体(类)没有显式地定义一个名为 serialVersionUID,类型为 long 的变量时,Java 序列化机制会根据编译的 class 自动生成一个 serialVersionUID 作序列化版本比较用,这种情况下,只有同一次编译生成的 class 才会生成相同的 serialVersionUID 。
如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,未作更改的类,就需要显式地定义一个名为 serialVersionUID,类型为 long 的变量,不修改这个变量值的序列化实体都可以相互进行串行化和反串行化。
其中阿里社区 Java 开发规范也就这一点做了强制要求。
线上问题由来
那么我们遇到的线上问题就是实体类没有显式定义一个 serialVersionUID,而上线前后又对同一个实体类做个更改,导致上线前放在 redis 中的用户登录信息在新的代码上线后,用户再次通过 redis 用户信息反序列化用户对象判定用户是否登录时,无法匹配的对应对象的 serialVersionUID,导致报错
问题总结
鉴于遇到的问题,后续针对实体类的建议:
对于 serialVersionUID 是用来验证版本一致性的。所以在做兼容性升级的时候,不要改变类中 serialVersionUID 的值。
如果一个类实现了 Serializable 接口,一定要记得定义 serialVersionUID,否则会发生异常。之所以会发生异常,是因为反序列化过程中做了校验,并且如果没有明确定义的话,会根据类的属性自动生成一个,但是只有同一次编译生成的 class 才会生成相同的 serialVersionUID,升级后再次编译的 class 将无法与上个版本兼容而异常。
补充说明
Serializable 和 Externalizable
一个类如果想启用序列化功能,需要通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法进行序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。
查看 Serializable 接口的源代码可以看到接口是空的,没有任何的方法或字段,仅用于标示可序列化的语义,如果一个类没有实现这个接口,想要被序列化的话,就会抛出 java.io.NotSerializableException 异常。
Java 中还提供了 Externalizable 接口,也可以实现它来提供序列化能力。
Externalizable 继承自 Serializable,该接口中定义了两个抽象方法:writeExternal()与 readExternal()。
当使用 Externalizable 接口来进行序列化与反序列化的时候需要开发人员重写 writeExternal()与 readExternal()方法。否则所有变量的值都会变成默认值。
Externalizable 接口和 Serializable 接口的区别:
Externalizable 继承了 Serializable,该接口中定义了两个抽象方法:writeExternal()与 readExternal()。当使用 Externalizable 接口来进行序列化与反序列化的时候需要开发人员重写 writeExternal()与 readExternal()方法。如果没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用 Externalizable 进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现 Externalizable 接口的类必须要提供一个 public 的无参的构造器。
transient
transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
开发环境配置
这里我使用的是 idea 开发工具,目前个人用过的 idea 开发工具版本有 2017 版和 2020 版,具体不同的版本实体类生成 serialVersionUID
idea2017 版
配置方案参考博文:https://developer.aliyun.com/article/831233
idea2020 版
目前我使用的是 2020 版,这里说一下 2020 版本的配置方案,2020 版的 idea 在插件库中已经无法搜索到插件了
这是需要通过配置来实现,点击【Settings...】>【Editor】>【Inspections】勾选图中两个复选框点击【OK】
这个时候如果你的代码实体类没有显式定义 serialVersionUID ,会提示
这时鼠标选中实体类,点击【Alt+Enter】组合键可以看到
点击红框选中内容即可生成 serialVersionUID
此博文既是记录个人日常问题处理,同时也希望对大家有所帮助
版权声明: 本文为 InfoQ 作者【六月的雨在infoQ】的原创文章。
原文链接:【http://xie.infoq.cn/article/15a5b2568d5ff4ea4a68ab434】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论