2009-04-27 11 views
9

クライアントにOracleデータベースがあり、オブジェクトがobjOutStream.writeObjectを介してblobフィールドとして永続化された場合、オブジェクトの現在の値はserialVersionUIDオブジェクトには何の変化、多分別のJVMのバージョン)を持っていないと、彼らは例外をデシリアライズしようとするとスローされます。オブジェクトが異なるserialVersionUIDを持つ場合にdb内で永続化されたオブジェクトのデコード方法

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

彼らは初めからserialVersionUIDために固定値を割り当てていない今、いくつかのものは変更されるようにその例外がスローされます。現在、彼らはデータを失うことは望まないので、オブジェクトを読み込んでデシリアライズし、XMLEncoderで再び永続化して、現在の「クラス互換性のない」エラーのような将来のエラーを避けることをお勧めします。

明らかにそのオブジェクトのために保持されているserialVersionUIDの2つの異なる値がありますので、データを読み込み、1つの値で試してみて、失敗したら他の値で試してみてください。クラスのserialVersionUIDthe ASM apiを使用しています。私は値を変更することができましたが、問題はクラスの変更をアクティブにする方法です。逆シリアル化されている場合は、serializedVersionUIDのクラスの修正版をobjInpStr.readObject()に取ります。

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

テストをして失敗する必要があります私はオブジェクト名がプロパティが CommissionResultあるReservationです(プロパティとして異なるserialVersionUID問題を持つオブジェクトを持つ)オブジェクトを取り、実環境をシミュレートするテストクラスを作りましたjava.io.InvalidClassException「ローカルクラス互換性のない」 私は、ファイルを保存し、 ・デ・ファイルを読み取るための、新しいものを使用した後、私はserialVersionUIDを変えたが、それは失敗していないので、ObjectInputStream.readObjectReservationの私の修正版を使用して でないことを意味するので、クラス。

すべてのアイデア?前もって感謝します。

!!!!!!!!!!!!! UPDATE:私はその前に言ったように

[OK]を、奇妙ないくつかのことが起こる、ストリームのserialVersionUIDを上書きするresultClassDescriptorを再定義することは可能であるが、 があるようです。この最後の値が 私はローカルクラスに値を指定しない場合に生成1である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のインスタンス内のjava.lang.String型の.CommissionResult.statusCode。posadas.ic.rules.common.commisionRules.CommissionResult

このエラーがスローされたとき8452040881660460728に変更 値はクラスが正常にデシリアライズされている、だから、何が起こる?場合、クラスは、-5239021592691549158の値を持っていたが間違ったクラスのキャストしようとするエラーはなぜですか?

おかげ

+0

HEX形式で新しいシリアルUIDと古いUIDを編集し、置き換えることができ、HEX形式でシリアルUIDを見つけることができ、同様の質問ですhttp: //stackoverflow.com/questions/444909/java-modifying-serialversionuid-of-binary-serialized-object –

+0

はい前にその質問を読んでいましたが、答えが見つからなかった、ダニエルは解決方法を見つけたが、詳細な方法と必要なもののようですが、私は彼に尋ねる方法が見つけられませんでした:S –

答えて

1

私は何かが欠けていることかもしれないが、あなたが必要以上に複雑な何かをしようとしているようにそれが鳴ります。

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

(b)読んでいるバイトストリームでは、古いシリアルUIDを新しいものに置き換えてから、それらの周りにObjectInputStreamをラップします。

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)です。その後、私は通常通り進む

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

を:私は何か他の人にこのシリアルバージョンIDを変更したいのであれば、私は、その後、私は25をオフセットで私の新しい番号を記述する必要がある、deserialisingを開始する前に

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

a)これは既に作成したテストです。永続化されたオブジェクトがハードコーディングを指定した値serialVersionUID、しかしthこの解決策は、各レコードを読み込み、デシリアライズして、後でもう一度試してみると(別のシリアルversionUIDvalueで再コンパイルして)、すべてのレコードがマイグレートされる(エンコードされるまで)ことを意味します。 xmlとして)しかし、4百万以上のレコードがあります:SとserialVersionIDの2つ以上のバージョンがある場合..解決策ではありません、どんな方法でもタンク –

+0

b)面白いと聞こえる、私はクラスを変更しようとしました。 ObjectInputStreamは、私がdinamically指定したserialVersionIDを使用しています。しかし.. serialVersionUIDを見つけてバイトストリームを変更し、それを変更する...どうすればいいですか?変更されたオブジェクトと同じ値を指定すると便利です...どのような例ですか?ありがとう –

+0

ホルヘ、私は彼は、2つの値がある場合、最初のシリアルIDでデータベースの半分を処理し、後半を処理するようにクラスを変更できると言っていると思いますか? –

1

データベースに複数のバージョンのクラスが格納されている場合は、それらをすべてデシリアライズして一貫したシリアライズフォーマットにアップグレードするのはかなり難しいかもしれません。

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

これはちょっと面倒ですが、非常に簡単です。

Javaシリアル化には、クラスの進化をサポートするための非常に便利な機能がいくつかあります。しかし、あなたは何をしているのかを意識しなければなりません。すべてのオブジェクトが実際に同じデータを持っているかもしれませんが、バージョンIDの維持には注意を払っていません。

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

+0

よく、それは悪くはありませんいくつかのことを私は試してみて、解決策と2つのバージョンがあれば簡単かもしれませんが、私はその場でIDの値を変更する方法を知りたいのですが、どのようにして要素の値がどのように失敗し、その値でプロセスを繰り返すかを保存しようとする方法はありません。列フラグを使用する方法は、すでにデシリアライズされていない要素を処理しないようにします。知っている、ありがとうerickson –

1

ObjectInputStream.readClassDescriptorを上書きすることで問題をハックすることができます。

XMLEncoderを使用すると、互換性のルールがほぼ同じであるため、実際にバージョンの移行に役立たない場合があります。実際には、ORMツールの助けを借りてオブジェクトをリレーショナル形式で永続化することが本当に必要です。

おそらく異なるserialVersionUIDは、javacによって異なる合成メンバーが生成されたために起こりました。警告を発してserialVersionUIDを入れてください。

+0

私のクライアントが行った変更の種類は、この場合は、特定の問題は、初期serialVersionUIDの欠如であったが、実際にはさらに多くの問題があると思いますかXMLEncoderのみを使用する場合プロパティが追加されていますか?ORMツールを使用するには最適なソリューションです。私は休止状態でオブジェクト全体を保持し、これを解決するために完全なスキーマを作成しますが、クライアントが時間を増やすとは思わないObjectInputStreamコードを読み込んでデシリアライズしようとします。 Tomに感謝します。 –

+0

XMLEncoderとJavaのシリアル化は、フィールドを追加するときと同じように動作します。シリアライゼーションの大きな利点は、カスタムシリアルフォーム(従来のシリアライズされたフォームの処理を含む)をオブジェクト内で実行できることです。 javaのソースを見てください。標準的なクラスを連載するためにいくつかの醜いハックを見るために豆。 –

15

ホルヘ私は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; 
    } 
} 
+0

これは本当に私を助けました。ありがとうございました。 –

+0

ありがとう!これは、私は大量のデータを回復するのに役立ちました! – stantonk

+0

@Bhushan Bhangale、こんにちは、あなたの答えを試したとき、java.io.StreamCorruptedException:無効なタイプコード:00は、私のコードに来る – Tenacious

1

あなたはデシベルにシリアル化されたデータを格納する場合、あなたはここで

関連する問題