簡體   English   中英

現在,當對象具有不同的serialVersionUID時,如何反序列化數據庫中持久存在的對象

[英]How to deserialize an object persisted in a db now when the object has different 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使用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 “本地類不兼容”,因為我在保存文件並使用新文件讀取de文件后更改了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,則該類成功反序列化,那么,會發生什么? 為什么這個錯誤試圖為錯誤的類投出?

謝謝

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

我可能會遺漏一些東西,但聽起來你正在嘗試做一些比必要更復雜的事情。 如果:

(a)您獲取當前的類定義(即源代碼)並將其串行UID硬編碼為舊的(或舊的),然后使用該類定義來反序列化序列化實例?

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

如果您有多個版本的類存儲在數據庫中,那么在一次傳遞中反序列化並將它們全部升級為一致的序列化格式可能會非常棘手。

如果可能,您可以使用列更改表以標記序列化對象是否已處理。 然后為每個serialVersionUID對表進行傳遞,在那里嘗試處理尚未處理的任何對象。 如果您的更新程序遇到它不處理的序列化對象,您可以捕獲InvalidClassException並繼續下一條記錄,記下版本號,以便您可以再次傳遞。

這有點乏味,但非常簡單。

Java序列化有一些非常好的功能來支持類的演變。 但是,您必須了解自己在做什么。 可能所有對象實際上具有相同的數據,但沒有注意維護版本ID。

一旦將所有對象更新為相同版本,就可以繼續使用序列化。 當你向類中添加新的字段時,要小心它們的默認值是有意義的(布爾值是假的,對象是空的,整數是零,等等)。

您應該能夠通過重寫ObjectInputStream.readClassDescriptor來解決這個問題。

使用XMLEncoder實際上不會幫助進行版本遷移,因為兼容性規則大致相同。 真正應該做的是在ORM工具的幫助下以關系形式保持對象。

可能由於javac生成的不同合成成員而發生了不同的serialVersionUID 查看警告並將serialVersionUID放入。

您可以找到HEX格式的串行UID,如果您在db中存儲序列化數據,您可以使用HEX格式的新串行UID編輯和替換舊UID

也許是一個黑客攻擊,但可能對某人有幫助:我有一個類似的問題,我通過復制違規類並將新類UID設置為0L(例如)來解決。 然后在執行序列化的代碼中,我將原始對象復制到新對象中並序列化。 然后,您可以更新代碼和反序列化代碼,以使用新類代替舊類。 盡管您仍然堅持使用新的類名,但這種方法非常有效。 但是,您可以重復此過程以恢復舊的類名。 最后,您有一個固定的UID。 提示我學到了很多困難:始終設置自己的UID!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM