我必须处理大量已编译的Java类,这些类没有明确指定serialVersionUID。因为它们的UID是由编译器任意生成的,所以许多需要序列化和反序列化的类最终会导致异常,即使实际的类定义匹配。 (当然,这是所有预期的行为。)

返回并修复所有第三方代码是不切实际的。

因此,我的问题是:是否有任何方法可以在serialVersionUID中使Java运行时忽略差异,并且只有在结构存在实际差异时才能反序列化?

有帮助吗?

解决方案

如果您有权访问代码库,则可以使用 Ant的SerialVer任务来插入代码库修改可序列化类的源代码中的 serialVersionUID ,并为所有人解决问题。

如果你不能,或者这不是一个选项(例如,如果你已经序列化了一些需要反序列化的对象),一个解决方案就是扩展 ObjectInputStream 。增加其行为以将流描述符的 serialVersionUID 与此描述符所代表的本地JVM中的类的 serialVersionUID 进行比较,并在不匹配的情况下使用本地类描述符。然后,只需使用此自定义类进行反序列化。像这样的东西(此消息):

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);
    }

    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 load time weaver agent 到VM,以便它可以将建议编入您现有的第三方课程。它很有趣虽然一旦你开始设置Aspectj,它将会给你带来非凡的用途。

HTH

STE

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top