12

私のクライアントには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つの異なる値があるので、データを読みたいのですが、1つの値で試して、失敗した場合は他の値で試してください。そうするために、をserialVersionUID使用してクラスの を 変更しようとしましたASM API . 値を変更することはできましたが、問題はクラスの変更をアクティブにする方法です。そのため、シリアル化解除されたときにobjInpStr.readObject()、変更したバージョンのクラスを特定の .xml で取得します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「ローカルクラスの互換性がありません」で失敗するはずですが、失敗しないため、変更されたバージョンのクラスが使用されていないことを意味します。serialVersionUIDObjectInputStream.readObjectReservation

何か案は?前もって感謝します。

!!!!!!!!!!!!!アップデート:

わかりました、resultClassDescriptor を再定義してストリーム serialVersionUID をオーバーライドすることは可能ですが、前に言ったように、クラスの 2 つのバージョンが永続化されているように見える奇妙なことが起こります。 value は、ローカル クラスに値を指定しない場合に生成されるものです。

-serialVersionUID の値を指定しない場合、デフォルト値 (8452040881660460728L) が使用されますが、他の値を持つオブジェクトをシリアル化解除することはできません。プロパティが他のプロパティであるというエラーがスローされます。タイプ。

-値 -5239021592691549158L を指定した場合、その値で永続化されたクラスは正常に逆シリアル化されますが、他の型は同じエラーになります。

これはエラートレースです:

潜在的に致命的な逆シリアル化操作。java.io.InvalidClassException: シリアライズされたクラス バージョンの不一致を上書きしています: ローカル serialVersionUID = -5239021592691549158 ストリーム serialVersionUID = 8452040881660460728 com.posadas.ic.rules.common.commisionRules.CommissionResult のインスタンス内のタイプ java.lang.String の CommissionResult.statusCode

このエラーがスローされたとき、クラスの値は -5239021592691549158 でした。値を 8452040881660460728 に変更すると、クラスは正常に逆シリアル化されます。間違ったクラスにキャストしようとするエラーはなぜですか?

ありがとう

4

6 に答える 6

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;
    }
}
于 2009-04-28T07:15:34.863 に答える
1

データベースに複数のバージョンのクラスが格納されている場合、それらすべてを逆シリアル化して、1回のパスで一貫したシリアル化形式にアップグレードするのはかなり難しい場合があります。

可能であれば、シリアル化されたオブジェクトがまだ処理されているかどうかを示す列を使用してテーブルを変更できます。次に、それぞれのテーブルをパスしますserialVersionUID。ここで、まだ処理されていないオブジェクトを処理しようとします。InvalidClassExceptionアップデーターが処理しないシリアル化されたオブジェクトに遭遇した場合は、をキャッチして次のレコードに進むことができます。バージョン番号をメモして、別のパスを作成できるようにします。

これは少し面倒ですが、非常に簡単です。

Javaシリアル化には、クラスの進化をサポートするための非常に優れた機能がいくつかあります。ただし、自分が何をしているのかを知っておく必要があります。すべてのオブジェクトが実際には同じデータを持っている可能性がありますが、バージョンIDの維持には注意が払われていません。

すべてのオブジェクトを同じバージョンに更新したら、引き続きシリアル化を使用できます。クラスに新しいフィールドを追加するときは、デフォルト値で意味があることに注意してください(ブール値はfalse、オブジェクトはnull、intはゼロなど)。

于 2009-04-27T22:41:37.270 に答える
1

をオーバーライドすることで、この問題を回避できるはずですObjectInputStream.readClassDescriptor

XMLEncoder互換性ルールはほとんど同じであるため、使用しても実際にはバージョンの移行には役立ちません。実際にすべきことは、ORM ツールを使用してオブジェクトをリレーショナル形式で永続化することです。

おそらく、serialVersionUIDjavac によって生成されるさまざまな合成メンバーが原因で、さまざまな s が発生しました。警告を頭に入れserialVersionUIDてください。

于 2009-04-28T00:24:06.820 に答える
1

HEX形式のシリアルUIDを見つけることができます。シリアル化されたデータをdbに保存すると、古いUIDを編集してHEX形式の新しいシリアルUIDに置き換えることができます

于 2012-03-22T07:09:01.420 に答える
1

何かが足りないかもしれませんが、必要以上に複雑なことをしようとしているようです。次の場合:

(a) 現在のクラス定義 (つまり、ソース コード) を取得し、そのシリアル UID を古いもの (または古いものの 1 つ) にハードコーディングし、そのクラス定義を使用してシリアル化されたインスタンスを逆シリアル化しますか?

(b) 読み込んでいるバイト ストリームで、ObjectInputStream をラップする前に、古いシリアル UID を新しいものに置き換えますか?

OK、明確にするために (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 桁の 16 進数です。注意深く見ると、2 行目の 9 バイト (18 桁) に、シリアル バージョン ID が始まる (1122...) ことがわかります。したがって、ここのデータでは (あなたのものは少し異なります)、シリアル バージョン ID のオフセットは 16 + 9 = 25 (または 16 進数で 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();
于 2009-04-27T22:16:43.663 に答える