次の 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() メソッドを呼び出すことです。
キーに (直接的または間接的に) マップへの循環参照が含まれている場合、逆シリアル化中に次の実行順序が可能です --- キーがハッシュマップの前にオブジェクト ストリームに書き込まれた場合:
- キーをインスタンス化する
- キーの属性を逆シリアル化します 2a. HashMap (キーによって直接的または間接的に指された) 2a-1 をデシリアライズします。HashMap 2a-2 をインスタンス化します。キーと値の読み取り 2a-3. キーで hashCode() を呼び出して、マップ 2b を再ハッシュします。キーの残りの属性を逆シリアル化します
2a-3 は 2b の前に実行されるため、キーの属性がまだ完全に逆シリアル化されていないため、hashCode() は間違った応答を返す可能性があります。
Wrapper から HashMap を削除するか、EntityTransformer クラスに移動すると、問題を修正できる理由が十分に説明されていません。