背景
Java Object Serialization 会使用对象中的serialVersionUID
私有静态常量长整型属性(private static final long
)作为该对象的版本号,反序列化时 JVM 会校验该版本号是否和序列化时的一致,如果不一致会导致序列化失败,抛出InvalidClassException
异常。
默认情况下,JVM 为每一个实现了Serializable
的接口的类生成一个serialVersionUID
,这个版本ID的计算规则是通过当前类信息(类名、属性、方法、修饰符等)生成的,所以当属性有变更时这个serialVersionUID
也一定会发生变更(简单新增空行、空格并不会影响serialVersionUID
生成)。
这个serialVersionUID
的生成,和所使用的JDK有关,不同的JDK可能会生成不一样的版本ID。
而且考虑到实际业务场景,变更属性修改类是常有的事,如果使用自动生成的版本ID很容易造成serialVersionUID
不一致的问题,导致反序列化失败。
所以最好是手动显示生成,大多数 JAVA IDE 都会提供自动生成版本ID的功能。
serialVersionUID不一致的兼容处理
普通序列化(Serialization) 和 反序列化(Deserialization):
public class SerializeTest {
public static void main(String[] args) throws Exception {
// Serialize to a file.
FileOutputStream f = new FileOutputStream("./tmp");
ObjectOutput s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Person());
s.flush();
// Deserialize a string and object from a file.
FileInputStream in = new FileInputStream("./tmp");
ObjectInputStream rd = new ObjectInputStream(in);
System.out.println((String) rd.readObject());
System.out.println((Person) rd.readObject());
}
}
使用ObjectOutputStream
序列化到文件中,然后使用ObjectInputStream
反序列化,这种方式,如果serialVersionUID
不一致反序列化时会报错;
处理这个不一致也很简单,既然反序列化时使用ObjectInputStream
来实现,那么这里自定义一个CompatibleInputStream
继承 ObjectInputStream
,然后重写readClassDescriptor
方法即可。
当遇到目标数据Class
版本号和本地Class
版本号不一致时,默认使用本地版本的Class
public class CompatibleInputStream extends ObjectInputStream {
public CompatibleInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
Class localClass; // the class in the local JVM that this descriptor represents.
try {
localClass = Class.forName(resultClassDescriptor.getName());
} catch (ClassNotFoundException e) {
return resultClassDescriptor;
}
ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass);
if (localClassDescriptor != null) { // only if class implements serializable
final long localSUID = localClassDescriptor.getSerialVersionUID();
final long streamSUID = resultClassDescriptor.getSerialVersionUID();
if (streamSUID != localSUID) { // check for serialVersionUID mismatch.
final StringBuffer s = new StringBuffer("Overriding serialized class version mismatch: ");
s.append("local serialVersionUID = ").append(localSUID);
s.append(" stream serialVersionUID = ").append(streamSUID);
System.out.println(s);
resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
}
}
return resultClassDescriptor;
}
}
测试:
public class SerializeTest {
public static void main(String[] args) throws Exception {
// Deserialize a string and object from a file with CompatibleInputStream.
FileInputStream in = new FileInputStream("./tmp");
CompatibleInputStream rd = new CompatibleInputStream(in);
System.out.println((String) rd.readObject());
System.out.println((Person) rd.readObject());
}
}
结果:
Today
Overriding serialized class version mismatch: local serialVersionUID = 3 stream serialVersionUID = 514127872132381055
com.wkw.study.jdk.serialize.Person@41cf53f9
参考: