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

发布于 2024-07-18 22:39:53 字数 5058 浏览 13 评论 0原文

我的客户端有一个 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 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 文件,但它没有失败,因此这意味着 ObjectInputStream.readObject 不是 使用我的 Reservation 类的修改版本。

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

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

好的,可以重新定义 resultClassDescriptor 来覆盖流serialVersionUID,但是,发生了一些奇怪的事情,正如我之前所说的那样 该类有两个版本,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.commissionRules.CommissionResult

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

谢谢

My client has an oracle data base and an object was persisted as a blob field via objOutStream.writeObject, the object now has a different serialVersionUID (even though the object has no change, maybe different jvm version) and when they try to de-serialize an exception is thrown:

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

They didn't assign a fixed value for serialVersionUID since the beginning so now that some thing changed that exception is thrown. Now they don't want to loose any data, to do so I think the best is to read the objects, de-serialize them, and persist them again via XMLEncoder to avoid future errors like the current "class incompatible" error.

Apparently there are 2 different values for the serialVersionUID persisted for that object so I want to read the data, try with one value and if it fails then try with the other value, To do so I've tried to change the serialVersionUID of the class using
the ASM api. I've been able to change the value but the problem is how to make active the change upon the class so when it is de-serialized the objInpStr.readObject() take my modified version of the class with my specific serializedVersionUID. I made a test class to simulate the real environment, I take an object (which has as property the object with different serialVersionUID problem) the object name is Reservation the property is
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();
    }

The test should fail with the java.io.InvalidClassException "local class incompatible"
because I changed the serialVersionUID after I saved the file and used a new one to read
de file but it doesn't fails so it means that the ObjectInputStream.readObject is not
using my modified version of the Reservation class.

Any Ideas? Thanks in advance.

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

Ok, it is possible to redefine the resultClassDescriptor to override the stream serialVersionUID, but, some thing strange happens, as I said before it seems there
are 2 versions of the class persisted, objects with serialVersionUID = -5239021592691549158L and others with value 8452040881660460728L this last value is
the one generated if I don't specify a value to the local class.

-If I don't specify a value for the serialVersionUID then the default value (8452040881660460728L) is used, but is not possible to de-serealize the objects that
has the other value, an error is thrown saying that a property is of an other type.

-If I specify the value -5239021592691549158L then classes persisted with that value
are successfully de-serialized, but not the others, same error of types.

this is the error trace :

Potentially Fatal Deserialization Operation. java.io.InvalidClassException: Overriding serialized class version mismatch: local serialVersionUID = -5239021592691549158 stream serialVersionUID = 8452040881660460728
java.lang.ClassCastException: cannot assign instance of java.util.HashMap to field com.posadas.ic.rules.common.commisionRules.CommissionResult.statusCode of type java.lang.String in instance of com.posadas.ic.rules.common.commisionRules.CommissionResult

When this error was thrown the class had the value of -5239021592691549158, if change
the value to 8452040881660460728 the class is successfully de-serialized, so, what happens? why is that error that tries to cast for wrong class ?

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

捎一片雪花 2024-07-25 22:39:53

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

Jorge I found one solution on http://forums.sun.com/thread.jspa?threadID=518416 which works.

Create the below class in your project. Whereever you creating object of ObjectInputStream, use DecompressibleInputStream instead and it deserializes the old object with the new version Id class.

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;
    }
}
故事灯 2024-07-25 22:39:53

如果数据库中存储了该类的多个版本,则一次将它们反序列化并升级为一致的序列化格式可能会非常棘手。

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

这有点乏味,但非常简单。

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

一旦将所有对象更新到同一版本,您就可以继续使用序列化。 在向类中添加新字段时要小心,确保它们的默认值有意义(布尔值为 false、对象为 null、整数为零等)。

If you've got multiple versions of the class stored in the database, it might be pretty tricky to deserialize and upgrade them all to a consistent serialization format in a single pass.

If possible, you might alter the table with a column to flag whether the serialized object has been processed yet. Then make passes over the table for each serialVersionUID, where you try to process any objects that haven't been dealt with yet. You can catch the InvalidClassException and go on to the next record if your updater encounters a serialized object that it doesn't handle, making a note of the version number so that you can make another pass.

This is a little tedious, but very simple.

Java serialization has some very nice features to support evolution of classes. However, you have to be aware of what you are doing. It may be that all of the objects actually have the same data, but no attention was paid to maintaining the version ID.

You could continue to use serialization once you get all of the objects updated to the same version. Just be careful as you add new fields to the class that they make sense with their default values (booleans are false, Objects are null, ints are zero, etc).

維他命╮ 2024-07-25 22:39:53

您应该能够通过重写ObjectInputStream.readClassDescriptor来解决这个问题。

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

不同的 serialVersionUID 可能是由于 javac 生成不同的合成成员而发生的。 在警告的开头并输入 serialVersionUID

You should be able to hack around the issue by overriding ObjectInputStream.readClassDescriptor.

Using XMLEncoder wont actually help with version migration as the compatibility rules are much the same. Really what should probably be doing is persisting the object in a relational form with the help of an ORM tool.

Probably the different serialVersionUIDs happened due to different synthetic members being generated by javac. Head the warnings and put serialVersionUID in.

回忆追雨的时光 2024-07-25 22:39:53

我可能遗漏了一些东西,但是听起来就像您正在尝试做一些比必要的更复杂的事情。 会发生什么情况

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

? (b) 在您正在读取的字节流中,您将旧的串行 UID 替换为新的,然后再用 ObjectInputStream 包裹它们?

好的,只是为了澄清 (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();

I may be missing something, but it sounds like you're trying to do something more complicated than necessary. What happens if:

(a) you take the current class definition (i.e. the source code) and hard-code its serial UID to the old one (or one of the old ones), then use that class definition to deserialise the serialised instances?

(b) in the byte stream you're reading, you replace the old serial UIDs with the new one before wrapping the ObjectInputStream around them?

OK, just to clarify (b). So for example, if I have a little class like this:

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

then when the data is serialised, it looks something like this:

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

Each line is 16 bytes, and each byte is 2 hex digits. If you look carefully, on the second line, 9 bytes (18 digits) in, you'll see the serial version ID starts (1122...).
So in our data here (yours will differ slightly), the offset of the serial version ID is 16 + 9 = 25 (or 0x19 in hex). So before I start deserialising, if I want to change this serial version ID to something else, then I need to write my new number at offset 25:

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

then I just proceed as normal:

ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(bytes));
MyClass obj = (MyClass) oin.readObject();
奈何桥上唱咆哮 2024-07-25 22:39:53

您可以找到十六进制格式的串行UID,如果您将序列化数据存储在数据库中,您可以编辑旧的UID并将其替换为十六进制格式的新串行UID

you can find the serial UID in HEX format, if you store serialized data in db, you can edit and replace the old UID with new serial UID in HEX format

甜心小果奶 2024-07-25 22:39:53

也许有点黑客行为,但可能对某人有帮助:我有一个类似的问题,我通过复制有问题的类并将新类 UID 设置为 0L(例如)来解决。 然后在进行序列化的代码中,我将原始对象复制到新对象中并序列化。 然后,您可以更新代码和反序列化代码,以使用新类代替旧类。 尽管您仍坚持使用新的类名,但效果非常好。 但是,您可以重复此过程来恢复旧的类名。 最后,您将获得一个您选择的固定 UID。 提示我经历了惨痛的教训:始终设置自己的 UID!

Bit of a hack perhaps, but might be helpful to someone: I had a similar problem, which I solved by duplicating the offending class and settings the new class UID to 0L (for example). Then in the code which does the serialisation I copied the original object into the new object and serialised out. Then you can update your code, and deserialisation code, to use the new class in place of the old. This works perfectly, although you are stuck with the new class name. You can repeat the process, however, to recover your old class name. At the end of it, you have a fixed UID of your choosing. Tip I learned the hard way: always set your own UID !

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文