关于java中的序列化
关于java中的序列化
Context
最近在精简项目代码,去除无用的依赖以及没有没有使用的模块。这其中免不了删除实体类中的无效字段。于是,,问题来了:
1 | java.io.InvalidClassException: entity.User; local class incompatible: stream classdesc serialVersionUID = 8550471390860600205, local class serialVersionUID = -7433272285406297333 |
用户未登录?
改完之后,在测试某接口的时候,只是返回用户未登录的信息,没想太多,,于是调用模拟测试账号登录行为的接口,然后重新测试刚才的接口,但还是返回用户未登录的信息。。
这就怪了,,正常来说,用户在正常登陆时,通过SQL查到用户信息后,保存部分字段到Redis中,之后每次用户发送请求时直接通过用户ID从Redis中获取用户相关信息。所以,,首先怀疑的是用户在登陆后发送其他请求时,通过Redis获取用户信息时出问题了,然后一点点看代码。调试到这儿的时候发生问题了👇
1 | ByteArrayInputStream bais = null; |
👆这是从Redis中获取用户信息后的反序列化时的部分代码,,正常来说,应该是返回从流中反序列化后得到的User实例。但实际调试过程中,运行到第6行之后,又跳到第10行,,咦,什么鬼??两个return
??嗯,,当然不是,肯定是在ois.readObject()
报错了,再看第8行,妈蛋,,打印报错信息的代码被注释了!OK,反注释之后就出现了Context中的报错信息。
序列化
报错信息大意为,,entity.User中本地类文件冲突,流中的类描述serialVersionUID与本地相应类的描述serialVersionUID不一致。出于安全考虑,反序列化失败。
关于序列化,之前不甚了解,,正好借此机会好好了解下~
什么时候使用序列化
- 把的内存中的对象持久化到一个文件中或者数据库中时候;
- 套接字在网络上传送对象的时候;
serialVersionUID
适用于Java的序列化机制。简单来说,Java 的序列化机制是通过判断类的serialVersionUID
来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的serialVersionUID
与本地相应实体类的serialVersionUID
进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException
。
serialVersionUID有两种显示的生成方式:
- 默认的1L,比如:
private static final long serialVersionUID = 1L
; - 根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = xxxxL
;
当一个类实现了Serializable
接口,如果没有显示的定义serialVersionUID
,Eclipse 会提供相应的提醒。面对这种情况,我们只需要在 Eclipse 中点击类中 warning 图标一下,Eclipse就会自动给定两种生成的方式。
当实现 java.io.Serializable 接口的类没有显式地定义一个serialVersionUID
变量时候,Java序列化机制会根据编译的 Class 自动生成一个serialVersionUID
作序列化版本比较用,这种情况下,如果 Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID
也不会变化的。
所以,在项目中 User 类中并没有显式的定义serialVersionUID
。所以,在我精简代码后,serialVersionUID
发生变化,导致反序列化失败。解决方法也很简单,,显式声明与流中相同的serialVersionUID
即可。
1 | private static final long serialVersionUID = 8550471390860600205L; |
静态变量序列化
下面代码中Test
实体类实现了Serializable
接口,并初始化了一个静态变量staticVar
。将对象序列化后,修改静态变量的数值,再将序列化对象读取出来,然后通过读取出来的对象获得静态变量的数值并打印出来那么结果是 10 还是 5 呢?
1 | public class Test implements Serializable { |
最后的输出是 10,对于无法理解的读者认为,打印的 staticVar 是从读取的对象里获得的,应该是保存时的状态才对。之所以打印 10 的原因在于序列化时,并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。
但是,serialVersionUID
也是static
的,那它是怎么被保存的呢?在jdk_7中,,
1 | java.io.ObjectOutputStream |
除此之外,,transient
关键字修饰的变量也不会被序列化。
父类的序列化
context:一个子类实现了Serializable
接口,它的父类都没有实现Serializable
接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。
解决:要想将父类对象也序列化,就需要让父类也实现Serializable
接口。如果父类不实现的话的,就需要有默认的无参的构造函数。在父类没有实现Serializable
接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。
另外,,如果父类已经实现Serializable
接口,其子类就无需再显式的实现Serializable
接口了。
参考链接:https://www.cnblogs.com/duanxz/p/3511695.html - 学无止境