【问题标题】:why serialization proxy pattern is not compatible with classes whose object graph contains circularities为什么序列化代理模式与对象图包含循环的类不兼容
【发布时间】:2014-01-17 01:56:01
【问题描述】:

我阅读了《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();
}
}

但是,它执行得非常好。那么,任何人都可以向我进一步解释。非常感谢。

【问题讨论】:

  • 这本书给出了什么理由?
  • @EJP:它解释说:“如果您尝试从其序列化代理的 readResolve 方法中调用对象上的方法,您将收到 ClassCastException,因为您还没有该对象,只有它的序列化代理。”

标签: java serialization


【解决方案1】:

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

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

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

在我们的例子中,我们将项目放在 SerializationProxy 对象中,但项目有一个对 原始对象(它们的父对象)。 那么当你想反序列化一个流时,反序列化的 SerializationProxy 的引用是 在项目的父字段中分配给原始对象的引用,并且您有 一个 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;
    }

}

【讨论】:

    【解决方案2】:

    书的作者有一个隐含的意思。

    如果我们想从其序列化代理的 readResolve 方法中调用对象上的方法,SerializationProxy 实例必须持有对代理实例的引用。一旦你这样做了,就会有一个循环。

        private static class SerializationProxy implements Serializable{
        private static final long serialVersionUID = 122L;
        
        private final Date start;
        private final Date end;
        private Period sp;//have to hold a reference to proxied instance
        
        SerializationProxy(Period p){
            this.start = p.start;
            this.end = p.end;
            this.sp = p;//comment out this line then no ClassCastException
        }
        
        private Object readResolve() {
            //sp.xx() call method of proxied instance
            return new Period(start, end);
        }
    }
    

    反序列化什么时候出现异常:

    java.lang.ClassCastException:无法将 Period$SerializationProxy 的实例分配给 Period$SerializationProxy 实例中 Period$SerializationProxy.sp 类型的字段

    原因由 Mladen 描述。

    【讨论】:

      猜你喜欢
      • 2012-03-12
      • 2020-06-15
      • 2017-10-11
      • 2018-01-24
      • 2014-10-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多