考虑以下三个类:
- EntityTransformer包含将实体与字符串相关联的映射
- Entity是一个包含 ID 的对象(由 equals / hashcode 使用),并且包含对EntityTransformer的引用(注意循环依赖)
- SomeWrapper包含一个EntityTransformer,并维护一个 Map 关联Entity的标识符和相应的Entity对象。
以下代码将创建一个 EntityTransformer 和一个 Wrapper,向 Wrapper 添加两个实体,对其进行序列化、反序列化并测试这两个实体是否存在:
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=whatever-a1, a2=whatever-a2}
错误的
真的
所以基本上,序列化以某种方式失败了,因为地图应该包含两个实体作为键。我怀疑 Entity 和 EntityTransformer 之间的循环依赖关系,实际上,如果我将 Entity 的 EntityManager 实例变量设为静态,它就可以工作。
问题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(直接或间接被 key 指向)2a-1。实例化 HashMap 2a-2。读取键和值 2a-3。在键上调用 hashCode() 以重新散列地图 2b。反序列化密钥的剩余属性
由于 2a-3 在 2b 之前执行,因此 hashCode() 可能会返回错误的答案,因为键的属性尚未完全反序列化。
现在这并不能完全解释为什么如果从 Wrapper 中删除 HashMap 或移至 EntityTransformer 类可以解决问题。