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

Jor*_*rez 12 java serialization serialversionuid

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

java.io.InvalidClassException: CommissionResult; local class incompatible: 
 stream classdesc serialVersionUID = 8452040881660460728, 
 local class serialVersionUID = -5239021592691549158
Run Code Online (Sandbox Code Playgroud)

它们serialVersionUID从一开始就没有为固定值赋值,所以现在有些东西改变了抛出异常.现在他们不想丢失任何数据,为此我认为最好的方法是读取对象,对它们进行反序列化,然后通过XMLEncoder再次保存它们,以避免将来出现类似当前"类不兼容"错误的错误.

显然有对2个不同的值serialVersionUID持续了那个对象,所以我想读取数据,尝试用一个值,如果失败则与其他值尝试,要做到这一点我已经试过改变serialVersionUID类使用 的ASM api.我已经能够更改值,但问题是如何激活类上的更改,因此当它被反序列化时,objInpStr.readObject()将我的修改版本的类与我的特定serializedVersionUID.我做了一个测试类来模拟真实的环境,我拿一个对象(它具有不同serialVersionUID问题的对象Reservation属性)对象名称属性是 CommissionResult:

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();
    }
Run Code Online (Sandbox Code Playgroud)

测试应该因java.io.InvalidClassException"本地类不兼容"而失败,因为我serialVersionUID在保存文件后使用了新文件来读取文件,但它没有失败,所以这意味着ObjectInputStream.readObject我没有使用我修改过的版本Reservation.

有任何想法吗?提前致谢.

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

好的,有可能重新定义resultClassDescriptor来覆盖流serialVersionUID,但是,有些奇怪的事情发生了,正如我之前说的那样,似乎有2个版本的类持久化,对象有serialVersionUID = -5239021592691549158L,其他有价值8452040881660460728L这个最后如果我没有为本地类指定值,则生成的值.

- 如果我没有为serialVersionUID指定值,则使用默认值(8452040881660460728L),但是无法取消实现具有其他值的对象,会抛出一个错误,指出属性属于其他属性类型.

- 如果我指定值-5239021592691549158L,那么持久化的类将成功反序列化,但不会是其他类型,类型的相同错误.

这是错误跟踪:

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

抛出此错误时,类的值为-5239021592691549158,如果将值更改为8452040881660460728,则该类成功反序列化,那么,会发生什么?为什么这个错误试图为错误的类投出?

谢谢

Bhu*_*ale 18

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Nei*_*fey 1

我可能错过了一些东西,但听起来你正在尝试做一些比必要的更复杂的事情。如果出现以下情况,会发生什么情况:

\n\n

(a) 您采用当前的类定义(即源代码)并将其串行 UID 硬编码为旧的(或旧的之一),然后使用该类定义来反序列化序列化的实例?

\n\n

(b) 在您正在读取的字节流中,在将 ObjectInputStream 包裹在旧的串行 UID 周围之前,您是否将旧的串行 UID 替换为新的?

\n\n

好的,只是为了澄清 (b)。举例来说,如果我有一堂这样的小课:

\n\n
  public static class MyClass implements Serializable {\n    static final long serialVersionUID = 0x1122334455667788L;\n    private int myField = 0xff;\n  }\n
Run Code Online (Sandbox Code Playgroud)\n\n

那么当数据被序列化时,它看起来像这样:

\n\n
ACED000573720011746573742E546573 \xc2\xac\xc3\xad..sr..test.Tes\n74244D79436C61737311223344556677 t$MyClass."3DUfw\n880200014900076D794669656C647870 ?...I..myFieldxp\n000000FF ...\xc3\xbf\n
Run Code Online (Sandbox Code Playgroud)\n\n

每行 16 个字节,每个字节 2 个十六进制数字。如果你仔细看,在第二行的 9 个字节(18 位数字)中,你会看到序列版本 ID 开头(1122...)。\n因此,在我们的数据中(你的数据会略有不同),偏移量串行版本 ID 的值为 16 + 9 = 25(或十六进制的 0x19)。因此,在开始反序列化之前,如果我想将此序列版本 ID 更改为其他内容,那么我需要在偏移量 25 处写入新编号:

\n\n
byte[] bytes = ... serialised data ...\nByteBuffer bb = ByteBuffer.wrap(bytes);\nbb.putLong(25, newSerialVersionUID);\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我就照常进行:

\n\n
ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));\nMyClass obj = (MyClass) oin.readObject();\n
Run Code Online (Sandbox Code Playgroud)\n