我们有一个使用java序列化序列化的类。这个类应该很少被修改。我想编写一个强制执行此规则的单元测试。当这个字段被修改时,它意味着很多变化,并且版本颠簸到依赖但分离的应用程序,这些应用程序使用此对象的java序列化来调用我们的。
一种想法就是使用自动生成的serialVersionUID。这个问题是它可以根据JVM的变化进行修改(这很难搞清楚)。
关于如何处理此事的任何其他想法?
谢天谢地,班级非常平坦。或者,换句话说,该类没有复杂的子类。它只是简单的java对象的集合,如字符串,整数和数组。答案 0 :(得分:2)
最简单的选择是使用反射API获取类接口列表,非静态和非瞬态字段,构造函数和方法,并声明它们没有更改。
这当然意味着您必须为受此序列化影响的每个类执行此操作(例如,如果您的根类具有其他对象的集合)。
作为参考,可以在Java serialization spec:
中找到SVUID算法
serialVersionUID
是使用流的签名计算的 反映类定义的字节。国家研究所 使用标准和技术(NIST)安全散列算法(SHA-1) 计算流的签名。前两个32位 数量用于形成64位散列。一个java.lang.DataOutputStream
用于将原始数据类型转换为 一系列字节。输入到流的值由。定义 类的Java虚拟机(VM)规范。该 流中的项目序列如下:
- 使用UTF编码编写的类名。
- 类修饰符写为32位整数。
- 按名称使用UTF编码排序的每个接口的名称。
- 对于按字段名称排序的类的每个字段(
private static
和private transient
字段除外):
- UTF编码字段的名称。
- 字段的修饰符,写为32位整数。
- UTF编码字段的描述符
- 如果存在类初始值设定项,请写出以下内容:
- 方法的名称
<clinit>
,采用UTF编码。- 方法的修饰符
java.lang.reflect.Modifier.STATIC
,写为32位整数。- 方法的描述符
()V
,采用UTF编码。- 对于按方法名称和签名排序的每个非私有构造函数:
- 方法的名称
<init>
,采用UTF编码。- 该方法的修饰符写为32位整数。
- UTF编码方法的描述符。
- 对于按方法名称和签名排序的每个非私有方法:
- UTF编码方法的名称。
- 该方法的修饰符写为32位整数。
- UTF编码方法的描述符。
- SHA-1算法在
DataOutputStream
生成的字节流上执行,并生成五个32位值sha[0..4]
。- 醇>
哈希值是从SHA-1消息摘要的第一个和第二个32位值汇编而来的。如果是消息的结果 摘要,五个32位字
H0 H1 H2 H3 H4
,是五个数组 名为sha的int
值,哈希值的计算方法如下:
long hash = ((sha[0] >>> 24) & 0xFF) | ((sha[0] >>> 16) & 0xFF) << 8 | ((sha[0] >>> 8) & 0xFF) << 16 | ((sha[0] >>> 0) & 0xFF) << 24 | ((sha[1] >>> 24) & 0xFF) << 32 | ((sha[1] >>> 16) & 0xFF) << 40 | ((sha[1] >>> 8) & 0xFF) << 48 | ((sha[1] >>> 0) & 0xFF) << 56;
以下是使用来自ASM 5.0框架的SerialVersionUIDAdder重新计算SVUID的代码:
SerialVersionUIDAdder svuidv = new SerialVersionUIDAdder(Opcodes.ASM5, null) {
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if ("serialVersionUID".equals(name)) {
return null;
}
return super.visitField(access, name, desc, signature, value);
}
protected void addSVUID(long svuid) {
if(svuid!=expectedsvid) {
throw new AssertionError("Serialization issue!");
}
}
};
InputStream is = AA.class.getResourceAsStream("/" + AA.class.getName().replace('.', '/') + ".class");
ClassReader cr = new ClassReader(is);
cr.accept(svuidv, ClassReader.SKIP_CODE);