8

シリアル化と serialVersionUID の使用に関するさまざまなブログを読みました。それらのほとんどは、シリアライズ可能なクラスの状態を維持するためにそれを使用することに言及しています。

私が持っているシナリオは次のとおりです。

古いserialVersionUIDと新しいserialVersionUIDを知っています。

古いserialVersionUIDを持つオブジェクトを読み込むときに、データを操作して新しいバージョンに適合させたいのですが、読み込んでいるオブジェクトが古いタイプの場合にのみ、これをやりたいと思います。

これは、非常に単純明快なことのように思えます。

オブジェクトが読み込まれるときに serialVersionUID を取得する方法はありますか?

シリアル化されたクラスの readObject メソッドが呼び出される前に InvalidClassException がスローされるため、そこにアクセスできません。

私が見つけた唯一のヒントは、ObjectInputStream をオーバーライドして readClassDescriptor() を利用できるようにすることですが、これはよくある問題に対する重い解決策のようです!

すべての助けに感謝します!

M

4

3 に答える 3

12

シリアライゼーションで古いバージョンのクラス/インターフェースをサポートする方法をいくつか紹介します。

マイグレーターを使用する

このような場合、Java プロジェクトで以下が必要になります。

  1. これらすべてのクラスを統合する不変のインターフェース
  2. として注釈が付けられたそのインターフェースの古い実装@Deprecated
  3. インターフェイスの新しい実装
  4. 廃止されたオブジェクトを新しいオブジェクトに変換するのに役立つ Migrator クラス

最初から、古いバージョンのオブジェクトを操作できるとは限らないことをお伝えします (シリアライズ可能なオブジェクトのバージョン管理に関する Oracle のドキュメントを参照してください)。簡単にするために、アップグレード中、クラスは常に定義したインターフェースを実装すると仮定しますIEntity

public interface IEntity extends Serializable {
   // your method definitions
}

そして、最初に次のクラスで作業するとします。

public class Entity implements IEntity {
   private static final long serialVersionUID = 123456789L;
   // fields and methods
}

Entityクラスを新しい serialVersionUID を持つ新しい実装にアップグレードする必要がある場合は、最初に注釈を付けます@Deprecated、名前を変更したり、別のパッケージに移動したりしないでください。

@Deprecated 
public class Entity implements IEntity {
private static final long serialVersionUID = 123456789L;
  // fields and methods
}

次のように、新しい serialVersionUID (重要) と追加のコンストラクターを使用して、新しい実装を作成します...

public class Entity_new implements IEntity {
  private static final long serialVersionUID = 5555558L;

  public Entity_new(IEntity){
     // Create a new instance of Entity_new copying the given IEntity
  }

}

もちろん、手順全体の中で最も重要な部分は、上記のコンストラクターをどのように実装するかです。古いタイプのいくつかのオブジェクトをEntity(たとえば を使用してObjectOutputStream) バイナリ ファイルとしてシリアライズし、 に移行したEntity_new場合、それらを のインスタンスとして解析してから、 のインスタンスにEntity変換できますEntity_new。次に例を示します。

public class Migrator {

   private final IEntity entity;
   private Class<? extends IEntity> newestClass = Entity_new.class;

   public Migrator(final IEntity entity){
    this.entity = entity;
   }

   public Migrator setNewestClass(Class<? extends IEntity> clazz){
     this.newestClass = clazz;
     return this;
   }

   public IEntity migrate() throws Exception {
     Constructor<? extends IEntity> constr =  
        newestClass.getConstructor(IEntity.class);
     return constr.newInstance(this.entity);
   }
}

もちろん、その特定のコンストラクターを必要としない、またはJava リフレクションを使用する他の代替手段があります。選択できる設計アプローチは他にもたくさんあります。上記のコードでは、簡単にするために、例外処理と null オブジェクトのチェックが完全に省略されていることにも注意してください。

シリアル化可能なジェネリック インターフェイスを設計する

該当する場合は、最初に、将来変更される可能性が低いクラスのインターフェイスを設計することを試みることができます。変更される可能性が非常に高い一連のプロパティをクラスに格納する必要がある場合はMap<String, Object>、この目的でを使用することを検討してください。Stringはプロパティ名/識別子を参照し、Objectは対応する値です。

readObject と writeObject をカスタマイズする

古いバージョンのサポートを提供する方法は他にもありますが、完全を期すために言及しますが、私が選択する方法はありません。クラス/インターフェースの現在および以前のすべてのバージョンの両方に対応する方法でprivate void readObject(ObjectInputStream in)実装できます。private void writeObject(ObjectOutputStream out)このようなことが常に実行可能で持続可能かどうかはわかりません。これらの方法の非常に面倒で長い実装になる可能性があります。

代替シリアライゼーション手法

これはOPの質問には答えませんが、取り上げる価値があると思います. JSON、YAML、XML などの ASCII 形式でオブジェクトをシリアル化することを検討してください。そのような場合、シリアライズ可能なインターフェイスを徹底的に再設計しない限り、拡張性はすぐに利用できます。拡張可能なバイナリ プロトコルを探している場合は、 BSON (バイナリ JSON) が適しています。おそらくこれは、Java で実装されていない可能性のあるソフトウェア間でオブジェクトの移植性を提供するための最良の方法です。

于 2011-02-15T15:52:27.823 に答える
6

同じに保つ必要がありserialVersionUIDます。シリアル化されたフィールドは、クラス自体のフィールドと一致する必要はありません。aを使用ObjectInputStream.readFieldsして定義しますserialPersistentFields(ただし、スペルが正しいことを確認してください)。

于 2011-02-15T15:32:48.817 に答える
1

これは簡単なことではありません。コードが両方のクラス タイプをサポートできる場合、これを処理する最善の方法は、serialVersionUID を変更せ、代わりにデータが新しいか古いかを検出し、それに応じてデータを読み取ることです。

古いデータから新しいデータへの 1 回限りのアップグレードを行いたい場合は、古いクラスと新しいクラスの両方がプロセスで利用できる (別々のクラスローダーなど) ある種のクラス ジャグリングをセットアップする必要があります。古いクラスを使用してデータを読み取り、新しいクラスにコピーして再書き込みする必要があります。ただし、これは間違いなく最善の方法ではありません。

つまり、serialVersionUID の変更は状態を維持する方法ではなく、非互換性 (つまり、救済が唯一の解決策である状況) を示す方法です。

于 2011-02-15T15:33:53.267 に答える