5

次の 3 つのクラスを検討してください。

  • EntityTransformerには、エンティティを文字列に関連付けるマップが含まれています
  • Entityは、ID (equals / hashcode によって使用される) を含むオブジェクトであり、EntityTransformerへの参照を含みます(循環依存に注意してください)。
  • SomeWrapperはEntityTransformerを含み、Entityの識別子と対応するEntityオブジェクトを関連付ける Map を維持します。

次のコードは、EntityTransformer と Wrapper を作成し、Wrapper に 2 つのエンティティを追加し、それをシリアル化し、逆シリアル化し、2 つのエンティティの存在をテストします。

public static void main(String[] args)
    throws Exception {

    EntityTransformer et = new EntityTransformer();
    Wrapper wr = new Wrapper(et);

    Entity a1 = wr.addEntity("a1");  // a1 and a2 are created internally by the Wrapper
    Entity a2 = wr.addEntity("a2");

    byte[] bs = object2Bytes(wr);
    wr = (SomeWrapper) bytes2Object(bs);

    System.out.println(wr.et.map);
    System.out.println(wr.et.map.containsKey(a1));
    System.out.println(wr.et.map.containsKey(a2));
}

出力は次のとおりです。

{a1=何でも-a1, a2=何でも-a2}

間違い

真実

基本的に、マップには両方のエンティティをキーとして含める必要があるため、シリアライゼーションは何らかの形で失敗しました。Entity と EntityTransformer の間の循環的な依存関係が疑われます。実際、Entity の EntityManager インスタンス変数を static にすると、機能します。

質問 1 : この循環的な依存関係に悩まされている場合、どうすればこの問題を克服できますか?

もう 1 つの非常に奇妙な点: ラッパー内の識別子とエンティティ間の関連付けを維持するマップを削除すると、すべて正常に動作します... ??

質問 2 : 誰かがここで何が起こっているのか理解できますか?

以下は、テストしたい場合の完全な機能コードです。

よろしくお願いします:)

public class SerializeTest {

public static class Entity
        implements Serializable
 {
    private EntityTransformer em;
    private String id;

    Entity(String id, EntityTransformer em) {
        this.id = id;
        this.em = em;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Entity other = (Entity) obj;
        if ((this.id == null) ? (other.id != null) : !this.id.equals(
            other.id)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 97 * hash + (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    public String toString() {
        return id;
    }
}

public static class EntityTransformer
    implements Serializable
{
    Map<Entity, String> map = new HashMap<Entity, String>();
}

public static class Wrapper
    implements Serializable
{
    EntityTransformer et;
    Map<String, Entity> eMap;

    public Wrapper(EntityTransformer b) {
        this.et = b;
        this.eMap = new HashMap<String, Entity>();
    }

    public Entity addEntity(String id) {
        Entity e = new Entity(id, et);
        et.map.put(e, "whatever-" + id);
        eMap.put(id, e);

        return e;
    }
}

public static void main(String[] args)
    throws Exception {
    EntityTransformer et = new EntityTransformer();
    Wrapper wr = new Wrapper(et);

    Entity a1 = wr.addEntity("a1");  // a1 and a2 are created internally by the Wrapper
    Entity a2 = wr.addEntity("a2");

    byte[] bs = object2Bytes(wr);
    wr = (Wrapper) bytes2Object(bs);

    System.out.println(wr.et.map);
    System.out.println(wr.et.map.containsKey(a1));
    System.out.println(wr.et.map.containsKey(a2));
}



public static Object bytes2Object(byte[] bytes)
    throws IOException, ClassNotFoundException {
    ObjectInputStream oi = null;
    Object o = null;
    try {
        oi = new ObjectInputStream(new ByteArrayInputStream(bytes));
        o = oi.readObject();
    }
    catch (IOException io) {
        throw io;
    }
    catch (ClassNotFoundException cne) {
        throw cne;
    }
    finally {
        if (oi != null) {
            oi.close();
        }
    }

    return o;
}

public static byte[] object2Bytes(Object o)
    throws IOException {
    ByteArrayOutputStream baos = null;
    ObjectOutputStream oo = null;
    byte[] bytes = null;
    try {
        baos = new ByteArrayOutputStream();
        oo = new ObjectOutputStream(baos);

        oo.writeObject(o);
        bytes = baos.toByteArray();
    }
    catch (IOException ex) {
        throw ex;
    }
    finally {
        if (oo != null) {
            oo.close();
        }
    }

    return bytes;
}
}

編集

この問題に関係している可能性のある内容についての適切な要約があります: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4957674

問題は、HashMap の readObject() 実装が、マップを再ハッシュするために、それらのキーが完全に逆シリアル化されているかどうかに関係なく、一部のキーの hashCode() メソッドを呼び出すことです。

キーに (直接的または間接的に) マップへの循環参照が含まれている場合、逆シリアル化中に次の実行順序が可能です --- キーがハッシュマップの前にオブジェクト ストリームに書き込まれた場合:

  1. キーをインスタンス化する
  2. キーの属性を逆シリアル化します 2a. HashMap (キーによって直接的または間接的に指された) 2a-1 をデシリアライズします。HashMap 2a-2 をインスタンス化します。キーと値の読み取り 2a-3. キーで hashCode() を呼び出して、マップ 2b を再ハッシュします。キーの残りの属性を逆シリアル化します

2a-3 は 2b の前に実行されるため、キーの属性がまだ完全に逆シリアル化されていないため、hashCode() は間違った応答を返す可能性があります。

Wrapper から HashMap を削除するか、EntityTransformer クラスに移動すると、問題を修正できる理由が十分に説明されていません。

4

3 に答える 3

4

これは循環初期化の問題です。Java シリアライゼーションは任意のサイクルを処理できますが、初期化は何らかの順序で行う必要があります。

( ) にその親 ( ) への参照が含まComponentれるAWT にも同様の問題があります。AWT が行うことは、 で親参照を作成することです。EntityContainerEntityTransformerComponent transient

transient Container parent;

したがって、それぞれが元に戻すComponent前に初期化を完了することができます。Container.readObject

    for(Component comp : component) {
        comp.parent = this;
于 2012-04-05T18:05:17.297 に答える
3

あなたがそうするなら、さらに見知らぬ人

Map<Entity, String> map = new HashMap<>(wr.et.map);
System.out.println(map.containsKey(a1));
System.out.println(map.containsKey(a2));

シリアライズとデシリアライズを行うと、正しい出力が得られます。

また:

for( Entity a : wr.et.map.keySet() ){
    System.out.println(a.toString());
    System.out.println(wr.et.map.containsKey(a));
}

与えます:

a1
false
a2
true

バグを見つけたと思います。ほとんどの場合、シリアライゼーションが何らかの形でハッシュを壊しました。実際、このバグを発見したと思います。

于 2012-04-05T16:27:18.890 に答える
0

シリアル化をオーバーライドして、シリアル化する前に参照をキー値に変換し、逆シリアル化で元に戻すことはできますか?

シリアル化するときに EntityTransformer のハッシュ キーを見つけて代わりにその値を使用し (parentKey と呼ばれる構造体に値を提供する場合があります)、参照を null にするのは非常に簡単なようです。次に、再シリアル化するときに、そのキー値に関連付けられている EntityTransformer を見つけて、その参照を割り当てます。

于 2012-04-05T15:55:29.007 に答える