为什么序列化代理模式与对象图包含圆形的类不兼容

时间:2014-01-17 01:56:01

标签: java serialization

我读过“Effective Java”,我被告知序列化代理模式与对象图包含圆形的类不兼容。其实我不明白。所以我写了一个样本来验证它。如你所见:

public class A implements Serializable{
    private B b;

    public A(){
    }

    public A(B b){
            this.b = b;
    }
}

public class B implements Serializable{
    private A a;

    public B(){
    }

    public B(A a){
            this.a = a;
    }
}

public class C implements Serializable{
private A a;
private B b;

public C(A a, B b){
    this.a = a;
    this.b = b;
}   

private Object writeReplace(){
    return new SerializationProxy(this);
}

private Object readObject(ObjectInputStream in) throws InvalidObjectException{
    throw new InvalidObjectException("Proxy Required!");
}

private static final class SerializationProxy implements Serializable{
    private final A a;
    private final B b;

    SerializationProxy(C c){
        this.a = c.a;
        this.b = c.b;
    }

    private Object readResolve(){
        return new C(a, b);
    }
}

public static void main(String args[]) throws Exception{
    C c = new C(new A(), new B());

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream out = new ObjectOutputStream(baos);
    out.writeObject(c);

    ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));

    c = (C)in.readObject();
}
}

然而,它执行得非常好。所以,任何人都可以向我进一步解释。非常感谢。

1 个答案:

答案 0 :(得分:3)

问题不在于循环引用,而在于具体情况 您可以引用对象图中的根对象。 在以下示例中,Container具有Items列表,但列表中的每个项目都有 对于Container的引用(对象图中的根对象!),因为它是父对象。

如果执行此代码,则输出将为ClassCastException的堆栈跟踪。 但是,如果您从Container的项目设置器中注释 item.setParent(this); 行 并再次运行代码,输出就可以了。

它发生的原因是使用writeReplace()实现序列化的方式。当你 用SerializationProxy替换原始对象,然后在SerializationProxy的流中, JVM将使用对SerializationProxy的引用替换对原始对象的任何引用 宾语。 (我不知道为什么它以这种方式实现,但它是)。 http://docs.oracle.com/javase/6/docs/platform/serialization/spec/input.html#5903

在我们的例子中,我们将项目放在SerializationProxy对象中,但是项目引用了 原始对象(他们的父母)。 然后,当您想要反序列化流时,反序列化的SerializationProxy的引用是 在Item的父字段中分配给原始对象的引用,你有 一个ClassCastException。

public class Main {

    public static void main(String[] args) throws Exception {
        try {
            Container c = new Container("Circular References Test");
            c.addItem(new Item("Item 1"));
            c.addItem(new Item("Item 2"));

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(baos);
            out.writeObject(c);

            ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));

            c = (Container)in.readObject();
            System.out.println("Container c has label '" + c.getLabel() + "' and has " + c.getItems().size() + " items.");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

}

class Container implements Serializable {
    private static final long serialVersionUID = 1L;
    private List<Item> items = new ArrayList<>();
    private String label = "";

    public Container() {
    }

    public Container(String label) {
        this.label = label;
    }

    public String getLabel() {
        return this.label;
    }
    public void setLabel(String label) {
        this.label = label;
    }

    public boolean addItem(Item item) {
        if(item != null) {
            item.setParent(this);
            return this.items.add(item);
        } else {
            return false;
        }
    }
    public boolean removeItem(Item item) {
        if(item != null) {
            item.setParent(null);
            return this.items.remove(item);
        } else {
            return false;
        }
    }

    public List<Item> getItems() {
        return new ArrayList<Item>(this.items);
    }

    private static final class SerializationProxy implements Serializable {

        private static final long serialVersionUID = 1L;
        private String containerLabel = "";
        private List<Item> items;
        public SerializationProxy(Container c){
            this.containerLabel = c.getLabel();
            this.items = new ArrayList<>(c.getItems());
        }

        private Object readResolve(){
            Container c = new Container(this.containerLabel);
            for(int i = 0; this.items != null && i < this.items.size(); i++) {
                c.addItem(this.items.get(i));
            }
            return c;
        }
    }

    private Object writeReplace(){
        return new SerializationProxy(this);
    }

    private Object readObject(ObjectInputStream in) throws InvalidObjectException{
        throw new InvalidObjectException("Proxy Required!");
    }
}

class Item implements Serializable {
    private static final long serialVersionUID = 1L;
    private Container parent = null;
    private String name = "";

    public Item() { 
    }
    public Item(String name) {
        this.name = name;
    }

    public Container getParent() {
        return this.parent;
    }
    public void setParent(Container parent) {
        this.parent = parent;
    }

    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }

}