serialversionuid
serializable 之 serialversionuid
本文不讲基本概念问题,如有需要 请另行查阅其他资料
最近在开发过程中遇到了InvalidClassException,也是基础不牢的缘故,导致不能快速的发现本质问题,进行有效的处理。于是侥幸的处理完这个问题之后,就想着好好的深入研究一下(也不算很深入,只是阅读了一下源码)。
正文
在java中,存在Serializable接口,对它的实现 无需override什么方法,JVM会在底层帮你实现(其实也不是很低层,也在Java中,但是不在本文讨论范围内),不过需要关注的是一个属性 – serialVersionUID(简称SUID),本文将对这个属性进行简单的讲解。
场景
- 定义:class Something implement Serializable – 未定义serialVersionUID
- 操作:
- output: 实例化Something对象,并持久化至文件中 – Objectoutputstream.writeObject(Something)
- input: 从文件中读取Something对象 – ObjectInputStream.readObject(byte[])
- 异常:
- 如果在output操作进行之后,对Something进行了部分的修改,然后再进行Input操作,将会抛出一个异常:InvalidClassException(“local class incompatible: stream classdesc serialVersionUID = $suid, local class serialVersionUID = ${osc.getSerialVersionUID()}”);
相关知识介绍
ObjectStreamClass
Serialization’s descriptor for classes. It contains the name and serialVersionUID of the class. The ObjectStreamClass for a specific class loaded in this Java VM can be found/created using the lookup method.
原文中可以理解成:每个Class(对象)在序列化时,都会伴随着这样一个关联的ObjectStreamClass对象,该对象中记录了Class的名字和serialVersionUID。
问题及源码
所有源码都进行了裁剪,如愿查看全部,请自行查阅
由于serialVersionUID属性 是optional,那ObjectStreamClass是如何获取到Class的serialVersionUID?
/**
* Returns explicit serial version UID value declared by given class, or
* null if none.
*/
private static Long getDeclaredSUID(Class<?> cl) {
try {
// 反射,获取SUID属性,并严格检查static final
field f = cl.getDeclaredField("serialVersionUID");
int mask = Modifier.STATIC | Modifier.FINAL;
// 这块的逻辑不是很清楚,但是debug的结果发现,一般是mask = 26,f.getModifiers() = 24
if ((f.getModifiers() & mask) == mask) {
f.setaccessible(true);
// 用于对SUID的定义long进行检查,但是存在一个问题
// 首先,所有的异常将会被和谐
// 其次对SUID的定义中,使用Long而不是long,将也会抛出异常
// 即SUID的定义必须为static fianl long
return Long.valueOf(f.getLong(null));
}
} catch (Exception ex) {}
return null;
}
那么,当Something中没有定义serialVersionUID属性呢?ObjectStreamClass又该如何是好?
/**
* Return the serialVersionUID for this class. The serialVersionUID
* defines a set of classes all with the same name that have evolved from a
* common root class and agree to be serialized and deserialized using a
* common format. NonSerializable classes have a serialVersionUID of 0L.
*
* @return the SUID of the class described by this descriptor
*/
public long getSerialVersionUID() {
// REMIND: synchronize instead of relying on volatile?
if (suid == null) {
suid = Accesscontroller.doPrivileged(
new PrivilegedAction<Long>() {
public Long run() {
// 其中computeDefaultSUID的内容比较复杂
// 简单的说:SUID与(类 & 接口 & 方法 & 属性 基本上是所有)的名称和修饰符都有关系
return computeDefaultSUID(cl);
// 一下为computeDefaultSUID的部分源码
// if (!Serializable.class.isassignableFrom(cl) || Proxy.isProxyClass(cl))
// { return 0L; }
// 正如该方法的注释所说:NonSerializable classes have a serialVersionUID of 0L.
}
}
);
}
return suid.longValue();
}
上面展示了ObjectStreamClass与SUID相关的部分内容,接着说明一下使用情况。
在ObjectOutputStream.writeObject时,会将对象对应的SUID一并序列化
/**
* from ObjectOutputStream
* Writes 【representation】 of given class descriptor to stream.
*/
private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException {
writeNonProxyDesc(desc, unshared);
}
// from ObjectOutputStream
private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
throws IOException {
bout.writeByte(TC_CLASSDESC);
desc.writeNonProxy(this);
}
/**
* from ObjectStreamClass
* Writes non-proxy class descriptor information to given output stream.
*/
void writeNonProxy(ObjectOutputStream out) throws IOException {
out.writeUTF(name); // 用于反序列化时 查找对应Class,并进行load
out.writeLong(getSerialVersionUID());
}
而在ObjectInputStream中会load对应Class,并获取其SUID,与Output写入的SUID进行对比
/**
* from ObjectInputStream
* Reads in and returns (possibly null) class descriptor. Sets passhandle
* to class descriptor's assigned handle. If class descriptor cannot be
* resolved to a class in the local VM, a ClassnotfoundException is
* associated with the class descriptor's handle.
*/
private ObjectStreamClass readClassDesc(boolean unshared) throws IOException {
return readNonProxyDesc(unshared);
}
// from ObjectInputStream
private ObjectStreamClass readNonProxyDesc(boolean unshared)
throws IOException {
ObjectStreamClass desc = new ObjectStreamClass();
// 读取byte数组,并创建ObjectStreamClass(class name & SUID)对象
ObjectStreamClass readDesc = readDesc = readClassDescriptor();
// 通过class.forname(readDesc.name),获取Class对象
if ((cl = resolveClass(readDesc)) == null)
{ resolveEx = new ClassNotFoundException("null class"); }
desc.initNonProxy(readDesc, cl, resolveEx, readClassDesc(false));
return desc;
}
// from ObjectStreamClass
void initNonProxy(ObjectStreamClass model, Class<?> cl,
ClassNotFoundException resolveEx, ObjectStreamClass superDesc)
throws InvalidClassException {
long suid = Long.valueOf(model.getSerialVersionUID());
osc = lookup(cl, true); // 根据Class获取对应ObjectStreamClass对象
// 异常的来源
if (model.serializable == osc.serializable && !cl.isArray() && suid != osc.getSerialVersionUID()) {
throw new InvalidClassException(osc.name,
"local class incompatible: stream classdesc serialVersionUID = " + suid +
", local class serialVersionUID = " + osc.getSerialVersionUID());
}
}
总结
在【Java自带序列化】中,对象的序列化和反序列化会关注 SUID,在序列化时写入,在反序列化时读出并对比。
个人认为,其主要目的是为了做到版本升级的兼容性(晦涩难懂)
直白的说,就是在Something A版本的时候,进行的序列化;现在升级成Something B版本,但是仍然希望可以读取以前的数据(不然数据就浪费了)。
在这种情况下,如果Something没有自定义SUID,则必然会出现InvalidClassException。于是为了实现兼容,需要在一开始的时候,在Something上定义SUID,以保障即使Something发生变化,SUID不会变化。(所以上面说的版本设计兼容性,就是SUID保持不变)。
其他
如果开始时Something A未定义SUID,并进行了序列化操作;
然来 想使用Something B(升级版)来进行反序列化。
也是有办法的:那就是将Something B的SUID设置成Something A的SUID
但是由于Something A没有自定义SUID,所以 需要获取Something A的SUID的 算法计算值(即由Java动态生成的值),可以通过*ObjectStreamClass.lookupAny(JavaBean.class).getSerialVersionUID()*来获取。
相关阅读
IDEA使用笔记(八)——自动生成 serialVersionUID 的设置
这个设置比较简单,也有一些博文已经写到了,为什么我还要写哪?(潜台词:因为我想凑一篇博文)我觉得学习,特别是编程学习是需要重复造轮子的
原文出处:https://www.cnblogs.com/duanxz/p/3511695.html实现Serializable接口的目的是为类可持久化,比如在网络传输或本地存储,为