背景
公司有个子服务较多,交互频繁的零碎,有一些须要共享传输的对象,它们通过 Java Object Serialization 后进行交互;然而因为一些不可形容的历史起因,这些对象存在多个版本,每个版本中的属性不统一,且未设置 serialVersionUID。
这阵子在做梳理 / 对立代码的工作,打算对立这些对象的版本和固定 serialVersionUID,然而因为服务较多,上线发版时会有一段新老版本共存的期间,所以得思考这些对象序列化的兼容问题,新的对象反序列化肯定得兼容老的对象
Java Object Serialization
Java 对象序列化(Serialization)是指将 Java 中的对象转为字节流,从而能够不便的存储或在网络中传输,反序列化(Deserialization)是指将字节流转位 Java 对象
个别状况下,Java Object Serialization 指的是利用 JDK 自带的性能对对象进行序列化 / 反序列化,而不是应用其余的序列化库进行(反)序列化
Java Object Serialization 中,要求对象必须实现 java.io.Serializable
接口,根本应用形式如下:
Serialization
// Serialize today's date to a file.
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutput s = new ObjectOutputStream(f);
s.writeObject("Today");
s.writeObject(new Date());
s.flush();
Deserialization
// Deserialize a string and date from a file.
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream s = new ObjectInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
serialVersionUID
private static final long serialVersionUID = 1L;
Java Object Serialization 会应用对象中的 serialVersionUID 常量属性作为该对象的版本号,进行反序列化时会校验该版本号是否统一,如果不统一会导致序列化失败,抛出 InvalidClassException
异样
默认状况下,JVM 为每一个实现了 Serializable 的接口的类生成一个 serialVersionUID(long),这个 ID 的计算规定是通过以后类信息(类名、属性等)去生成的,所以当属性有变更时这个 serialVersionUID 也肯定会产生变更
这个 serialVersionUID 的生成,和所应用的 JDK 无关,不同的 JDK 可能会生成不一样的版本号,所以最好是手动生成一个,大多数 JAVA IDE 都会提供这个生成的性能
而且思考到理论业务场景,变更属性是常有的事,如果应用主动生成的版本号很容易造成 serialVersionUID 不统一的问题,导致反序列化失败
serialVersionUID 不统一时的兼容解决
解决这个不统一也很简略,既然反序列化时应用 ObjectInputStream 来实现,那么这里自定义一个 CompatibleInputStream 继承 ObjectInputStream,并且重写 readClassDescriptor 办法即可
当遇到指标数据 Class 版本号和本地 Class 版本号不统一时,默认应用本地版本的 Class 即可
public class CompatibleInputStream extends ObjectInputStream {private static Logger logger = LoggerFactory.getLogger(CompatibleInputStream.class);
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) {logger.error("No local class for" + resultClassDescriptor.getName(), 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);
Exception e = new InvalidClassException(s.toString());
logger.error("Potentially Fatal Deserialization Operation.", e);
resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
}
}
return resultClassDescriptor;
}
}
以上要害代码摘自 https://stackoverflow.com/a/1816711/6507948
应用形式:
// Deserialize a string and date from a file.
FileInputStream in = new FileInputStream("tmp");
// 反序列化时应用下面的 CompatibleInputStream 即可
ObjectInputStream s = new CompatibleInputStream(in);
String today = (String)s.readObject();
Date date = (Date)s.readObject();
Java 中支流的对象序列化库
- JBoss Marshalling
- Jackson
- Apache avro
- CBOR
- MessagePack
- Amazon Ion
- Kryo
- FST
参考
- Java Object Serialization Specification
- https://stackoverflow.com/a/1816711/6507948