serialversionuid不一致引发异常?教你如何解决版本兼容问题!

吉云

得,今天来说说我捣鼓 `serialVersionUID` 这玩意儿的经历。

起因是这样的,我之前在做一个项目,里面有不少地方需要把 Java 对象存起来,或者是在网络上传来传去。代码写得很顺,对象该序列化序列化,该反序列化反序列化,跑得好好的,我也没太在意。

直到有一次,我对其中一个常用的类,加点新功能,也就是多几个方法,没动里面的字段。改完之后本地跑跑没啥问题,就提交。结果没过多久,线上环境(或者是测试环境,记不清)就开始报警,说是反序列化的时候出错,抛个 `InvalidClassException`,错误信息里就提到 `serialVersionUID` 不匹配。

serialversionuid不一致引发异常?教你如何解决版本兼容问题!

当时我就有点纳闷,这 `serialVersionUID` 是个我代码里也没写过这东西。于是就开始查资料,翻看报错的日志。搞半天才明白,原来 Java 在序列化一个对象的时候,会给这个对象的类算一个版本号,就是这个 `serialVersionUID`。等反序列化的时候,它会拿当前代码里这个类的版本号,跟序列化时存下来的那个版本号对一下。如果对不上,它就觉得这俩类可能不兼容,直接报错,不给你反序列化。

那我的问题出在哪?因为我没手动指定这个 `serialVersionUID`,Java 虚拟机(JVM)就自动帮我生成一个。但这个自动生成的算法比较“敏感”,它会根据类的很多细节来算,包括字段、方法签名等等。我虽然只是加几个方法,没动字段,但在 JVM 看来,这个类已经变,所以自动生成的 `serialVersionUID` 就跟之前不一样。旧数据是用旧版本号序列化的,新代码用的是新版本号,一对比,自然就炸。

找到解决办法

知道原因,解决起来就好办。最直接的办法,就是在我的类里面,手动给它指定一个 `serialVersionUID`。就像这样:

private static final long serialVersionUID = 1L;

这行代码加到类里面就行。这个 `1L` 就是我给它的版本号。你可以随便写个 long 类型的数字,或者用 IDE(比如 Eclipse、IntelliJ IDEA)提供的功能帮你生成一个看起来更“随机”的。关键在于,一旦你手动指定,只要你不去改这个数字,JVM 就不会再自动给你算。这样一来,就算你以后再加方法,或者改点别的非序列化相关的细节,只要你不动这个 `serialVersionUID`,序列化和反序列化的时候版本号就能对得上。

serialversionuid不一致引发异常?教你如何解决版本兼容问题!

这也不是说加这行代码就万事大吉。如果你真的改类的结构,比如删掉一个字段,或者改字段的类型,那即使 `serialVersionUID` 没变,反序列化旧数据的时候还是可能出问题(比如数据丢失或者类型转换错误)。这时候,按理说就应该更新这个 `serialVersionUID`,表示类确实发生不兼容的变更。

处理版本不一致的情况

后来我还遇到更复杂点的情况,就是比如在分布式系统里,服务 A 用的是 V1 版本的类序列化数据,然后服务 B 用的是 V2 版本的类去反序列化。就算两个版本都手动指定 `serialVersionUID`,如果指定的数字不一样,还是会报那个不匹配的错。

这时候咋办?

  • 最稳妥的当然是尽量保证所有地方用的类版本是一致的,`serialVersionUID` 也一致。但这有时候不太现实,升级总是有先后的。
  • serialversionuid不一致引发异常?教你如何解决版本兼容问题!

  • 如果确定两个版本是兼容的(比如只是加个非必要字段),有人会想办法绕过这个检查。我看到网上有说可以自定义一个 `ObjectInputStream`,重写里面的某个方法,让它在版本号不匹配的时候不报错,强行用本地的类信息来反序列化。不过我个人觉得这招有点悬,万一两个版本真不兼容,硬读出来的数据可能就是错的,还不如直接报错来得安全。所以我自己没实际用过这种方法。
  • 我现在的做法是,对于需要序列化的类,强制要求必须手动指定 `serialVersionUID`。并且在团队内约定如果只是做兼容性修改(比如加方法、加非关键字段),就保持 `serialVersionUID` 不变;如果做不兼容的修改(删字段、改字段类型),那就必须更新 `serialVersionUID` 的值,并且要考虑好数据迁移或者兼容处理的方案。

自从被这个 `serialVersionUID` 坑过一次之后,我现在写涉及到序列化的类,都会老老实实地加上 `private static final long serialVersionUID = xxxL;` 这一行。虽然看起来是件小事,但确实能避免掉不少后期可能出现的麻烦。算是我实践中踩出来的一个小经验,分享给大家。

免责声明:由于无法甄别是否为投稿用户创作以及文章的准确性,本站尊重并保护知识产权,根据《信息网络传播权保护条例》,如我们转载的作品侵犯了您的权利,请您通知我们,请将本侵权页面网址发送邮件到qingge@88.com,深感抱歉,我们会做删除处理。

目录[+]