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

时间:2009-04-27 22:02:54

标签: 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属性是 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();
    }

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

任何想法?提前谢谢。

!!!!!!!!!!!!! UPDATE:

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

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

- 如果我指定值-5239021592691549158L,则类持久保存该值 成功反序列化,但不是其他类型,相同的类型错误。

这是错误跟踪:

可能致命的反序列化操作。 java.io.InvalidClassException:重写序列化类版本不匹配:本地serialVersionUID = -5239021592691549158 stream 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的值,那么,会发生什么?为什么那个试图为错误的类投出的错误?

由于

6 个答案:

答案 0 :(得分:17)

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

答案 1 :(得分:1)

我可能会遗漏某些东西,但听起来就像你正在尝试做一些比必要更复杂的事情。如果出现以下情况:

(a)你获取当前的类定义(即源代码)并将其串行UID硬编码为旧的(或旧的),然后使用该类定义对序列化实例进行反序列化? / p>

(b)在您正在读取的字节流中,在将ObjectInputStream包装在它们周围之前,用新的串行UID替换旧的串行UID?

好的,只是为了澄清(b)。所以,举个例子,如果我有一个像这样的小课:

  public static class MyClass implements Serializable {
    static final long serialVersionUID = 0x1122334455667788L;
    private int myField = 0xff;
  }

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

ACED000573720011746573742E546573 ’..sr..test.Tes
74244D79436C61737311223344556677 t$MyClass."3DUfw
880200014900076D794669656C647870 ?...I..myFieldxp
000000FF ...ÿ

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

byte[] bytes = ... serialised data ...
ByteBuffer bb = ByteBuffer.wrap(bytes);
bb.putLong(25, newSerialVersionUID);

然后我就像往常一样继续:

ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));
MyClass obj = (MyClass) oin.readObject();

答案 2 :(得分:1)

如果你有多个版本的类存储在数据库中,那么反序列化并将它们全部升级到一次性的一致序列化格式可能会非常棘手。

如果可能,您可以使用列更改表以标记序列化对象是否已被处理。然后在每个serialVersionUID的表格上进行传递,在那里您尝试处理尚未处理的任何对象。如果您的更新程序遇到它未处理的序列化对象,您可以捕获InvalidClassException并继续下一条记录,记下版本号,以便您可以再次传递。

这有点单调乏味,但很简单。

Java序列化有一些很好的功能来支持类的演变。但是,您必须了解自己在做什么。可能所有对象实际上具有相同的数据,但没有注意维护版本ID。

一旦将所有对象更新到同一版本,就可以继续使用序列化。当你向类中添加新的字段时,请注意它们的默认值(布尔值为false,对象为空,int为零等)。

答案 3 :(得分:1)

您应该可以通过覆盖ObjectInputStream.readClassDescriptor来解决问题。

使用XMLEncoder实际上不会帮助进行版本迁移,因为兼容性规则大致相同。真正应该做的是在ORM工具的帮助下以关系形式保持对象。

由于javac生成了不同的合成成员,可能发生了不同的serialVersionUID。领导警告并将serialVersionUID放入。

答案 4 :(得分:1)

你可以找到HEX格式的串行UID,如果你在db中存储序列化数据,你可以用HEX格式的新串口UID编辑和替换旧的UID

答案 5 :(得分:0)

也许有些破绽,但可能对某人有所帮助:我有一个类似的问题,我通过复制有问题的类并将新类的UID设置为0L来解决了这个问题。然后,在执行序列化的代码中,我将原始对象复制到新对象中并进行序列化。然后,您可以更新代码和反序列化代码,以使用新类代替旧类。尽管您仍然使用新的类名,但这仍然可以完美地工作。但是,您可以重复此过程以恢复您的旧类名称。最后,您可以选择一个固定的UID。提示:我学到了很难的方法:始终设置自己的UID!