java - 当对象具有不同的 serialVersionUID 时,如何反序列化持久保存在数据库中的对象

标签 java serialization serialversionuid

我的客户端有一个 oracle 数据库,一个对象通过 objOutStream.writeObject 作为 blob 字段持久化,该对象现在有一个不同的 serialVersionUID(即使该对象没有变化,可能是不同的 jvm版本)并且当他们尝试反序列化时抛出异常:

java.io.InvalidClassException: CommissionResult; local class incompatible: 
 stream classdesc serialVersionUID = 8452040881660460728, 
 local class serialVersionUID = -5239021592691549158

他们从一开始就没有为 serialVersionUID 分配固定值,所以现在某些事情发生了变化,抛出了异常。现在他们不想丢失任何数据,为此我认为最好的方法是读取对象,反序列化它们,然后通过 XMLEncoder 再次持久化它们以避免将来出现诸如当前“类不兼容”错误之类的错误。

显然,该对象的 serialVersionUID 有 2 个不同的值,所以我想读取数据,尝试使用一个值,如果失败则尝试使用另一个值,为此我我尝试使用以下方法更改类的 serialVersionUID the ASM api .我已经能够更改该值,但问题是如何在类上激活更改,因此当它被反序列化时 objInpStr.readObject() 将我修改后的类版本与我的特定 serializedVersionUID。我制作了一个测试类来模拟真实环境,我取了一个对象(它的属性是具有不同 serialVersionUID 问题的对象)对象名称是 Reservation 属性是 佣金结果:

public class Reservation implements java.io.Serializable {


    private CommissionResult commissionResult = null;

}


public class CommissionResult implements java.io.Serializable{



}


import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.commons.SerialVersionUIDAdder;

public class SerialVersionUIDRedefiner extends ClassLoader {


    public void workWithFiles() {
        try {
            Reservation res = new Reservation();
            FileOutputStream f = new FileOutputStream("/home/xabstract/tempo/res.ser");
        ObjectOutputStream out = new ObjectOutputStream(f);

            out.writeObject(res);

            out.flush();
            out.close();

            ClassWriter cw = new ClassWriter(0); 
             ClassVisitor sv = new SerialVersionUIDAdder(cw); //assigns a real serialVersionUID 
             ClassVisitor ca = new MyOwnClassAdapter(sv); //asigns my specific serialVerionUID value
             ClassReader cr=new  ClassReader("Reservation"); 
              cr.accept(ca, 0); 

             SerialVersionUIDRedefiner   loader= new SerialVersionUIDRedefiner(); 
             byte[] code = cw.toByteArray();
             Class exampleClass =        loader.defineClass("Reservation", code, 0, code.length); //at this point the class Reservation has an especific serialVersionUID value that I put with MyOwnClassAdapter

             loader.resolveClass(exampleClass);
             loader.loadClass("Reservation");
             DeserializerThread dt=new DeserializerThread();
             dt.setContextClassLoader(loader);
             dt.run();
    } catch (Exception e) {
            e.printStackTrace();
    }}



import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class DeserializerThread extends Thread {

    public void run() {
        try {
            FileInputStream f2;

            f2 = new FileInputStream("/home/xabstract/tempo/res.ser");

             ObjectInputStream in = new ObjectInputStream(f2);


            Reservation c1 = (Reservation)in.readObject();



            System.out.println(c1);

        } catch (Exception e) {

            e.printStackTrace();
        }
        stop();
    }
}

MyOwnClassAdapter Relevant code:



public void visitEnd() {
        // asign SVUID and add it to the class

            try {

                cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC,
                        "serialVersionUID",
                        "J",
                        null,
                        new Long(-11001));//computeSVUID()));
            } catch (Throwable e) {
                e.printStackTrace();
                throw new RuntimeException("Error while computing SVUID for x"
                        , e);
            }


        super.visitEnd();
    }

测试应该失败并返回 java.io.InvalidClassException“本地类不兼容” 因为我在保存文件后更改了 serialVersionUID 并使用新的读取 de 文件,但它没有失败,所以这意味着 ObjectInputStream.readObject 不是 使用我修改后的 Reservation 类。

有什么想法吗?提前致谢。

!!!!!!!!!!!!!更新:

好的,可以重新定义 resultClassDescriptor 来覆盖流 serialVersionUID,但是,有些奇怪的事情发生了,正如我之前所说的那样 是否保留了该类的 2 个版本,serialVersionUID = -5239021592691549158L 的对象和其他值为 8452040881660460728L 的对象最后一个值为 如果我没有为本地类指定值,则生成一个。

-如果我没有为 serialVersionUID 指定值,则使用默认值 (8452040881660460728L),但无法反序列化 具有其他值,则会抛出一个错误,指出属性属于其他类型。

-如果我指定值 -5239021592691549158L,那么类将保留该值 成功反序列化,但其他人没有,类型相同的错误。

这是错误跟踪:

可能致命的反序列化操作。 java.io.InvalidClassException:覆盖序列化类版本不匹配:本地 serialVersionUID = -5239021592691549158 流 serialVersionUID = 8452040881660460728 java.lang.ClassCastException:无法将 java.util.HashMap 的实例分配给 com.posadas.ic.rules 实例中类型为 java.lang.String 的字段 com.posadas.ic.rules.common.commisionRules.CommissionResult.statusCode。 common.commisionRules.CommissionResult

当抛出这个错误时,类的值为 -5239021592691549158,如果更改 8452040881660460728 类的值已成功反序列化,那么,会发生什么?为什么会尝试为错误的类转换错误?

谢谢

最佳答案

Jorge 我在 http://forums.sun.com/thread.jspa?threadID=518416 上找到了一个解决方案哪个有效。

在您的项目中创建以下类。无论您在何处创建 ObjectInputStream 的对象,都应改用 DecompressibleInputStream,它会使用新版本的 Id 类反序列化旧对象。

public class DecompressibleInputStream extends ObjectInputStream {

    public DecompressibleInputStream(InputStream in) throws IOException {
        super(in);
    }


    protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
        ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor
        Class localClass = Class.forName(resultClassDescriptor.getName()); // the class in the local JVM that this descriptor represents.
        if (localClass == null) {
            System.out.println("No local class for " + resultClassDescriptor.getName());
            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());
                System.out.println("Potentially Fatal Deserialization Operation. " + e);
                resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization
            }
        }
        return resultClassDescriptor;
    }
}

关于java - 当对象具有不同的 serialVersionUID 时,如何反序列化持久保存在数据库中的对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/795470/

相关文章:

php - 在 WooCommerce 3 中获取产品属性详细信息

c++ - 升级 Boost Serialization 与二进制归档 armv7 到 arm64 的兼容性

java - Java中如何根据字符串列表创建对象?

Java/Spring MVC 起始页面项目?

java - 如何在 Java 中使用笔?

java - 如何仅重新加载部分代码 - Java

json - 如何使用 Jackson 将 Scala 值类序列化为字符串?

java - POJO类中序列版本id的唯一性

java - 这里使用serialVersionUID的目的是什么?

java - 为什么依赖 ObjectStreamClass.getSerialVersionUID 不安全?