我必须使用大量未明确指定 serialVersionUID 的已编译 Java 类。由于它们的 UID 是由编译器任意生成的,因此许多需要序列化和反序列化的类最终会导致异常,即使实际的类定义匹配。(当然,这都是预期的行为。)
我返回并修复所有这些第 3 方代码是不切实际的。
因此,我的问题是:有什么方法可以让 Java 运行时忽略serialVersionUID 中的差异,并且只有在结构存在实际差异时才无法反序列化?
我必须使用大量未明确指定 serialVersionUID 的已编译 Java 类。由于它们的 UID 是由编译器任意生成的,因此许多需要序列化和反序列化的类最终会导致异常,即使实际的类定义匹配。(当然,这都是预期的行为。)
我返回并修复所有这些第 3 方代码是不切实际的。
因此,我的问题是:有什么方法可以让 Java 运行时忽略serialVersionUID 中的差异,并且只有在结构存在实际差异时才无法反序列化?
如果您有权访问代码库,则可以使用Ant 的 SerialVer 任务在可序列化类的源代码中插入和修改,serialVersionUID
并一劳永逸地解决问题。
如果你不能,或者这不是一个选项(例如,如果你已经序列化了一些需要反序列化的对象),一种解决方案是扩展ObjectInputStream
. 增强其行为以将serialVersionUID
流描述符的 与serialVersionUID
该描述符所代表的本地 JVM 中的类的 进行比较,并在不匹配的情况下使用本地类描述符。然后,只需使用这个自定义类进行反序列化。像这样的东西(归功于此消息):
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DecompressibleInputStream extends ObjectInputStream {
private static Logger logger = LoggerFactory.getLogger(DecompressibleInputStream.class);
public DecompressibleInputStream(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;
}
}
解决这个问题有多不切实际?如果你有源并且可以重建,你能不能只在整个代码库上运行一个脚本来插入一个
private long serialVersionUID = 1L;
到处都是?
使用 CGLIB 将它们插入二进制类?
运行时的序列化错误明确地告诉您 ID 应该是什么。只需更改您的类以将它们声明为 ID,一切都会好起来的。这确实涉及您进行更改,但我认为这无法避免
您可以使用 Aspectj 在加载时将字段“引入”到每个可序列化类中。我将首先使用包将标记接口引入每个类,然后使用 serialVersionUID 的类文件的哈希引入字段
public aspect SerializationIntroducerAspect {
// introduce marker into each class in the org.simple package
declare parents: (org.simple.*) implements SerialIdIntroduced;
public interface SerialIdIntroduced{}
// add the field to each class marked with the interface above.
private long SerialIdIntroduced.serialVersionUID = createIdFromHash();
private long SerialIdIntroduced.createIdFromHash()
{
if(serialVersionUID == 0)
{
serialVersionUID = getClass().hashCode();
}
return serialVersionUID;
}
}
您需要将aspectj 加载时间编织器代理添加到 VM,以便它可以将建议编织到您现有的 3rd 方类中。有趣的是,一旦您开始设置 Aspectj,您将投入使用的次数非常多。
高温高压
斯特