第七十八条 虑用序列化代理代理序列化实例


实现了序列化的类,在序列化和反序列化时,由 writeObject 方法把对象写入磁盘,实例的创建是由readObject方法来完成的,因此,每次反序列化出来的对象的地址值都不一样,为了解决这个问题,可以编写 readResolve() 方法,或者用枚举代替实例类。有没有其他的办法呢?我们知道,执行 readObject 时,外部可能会通过伪字节流和内部盗用域来攻击程序,它就是伪造一个字节流,通过 readObject 读取,来改变对象的成员变量的值。鉴于此,我们今天提出的方法是代理。


public class Period implements Serializable {

    private static final long serialVersionUID = 234567895125541122L;
    private final Date start;
    private final Date end;

    public Period(Date start, Date end) {

        if (null == start || null == end || start.after(end)) {

            throw new IllegalArgumentException("时间数据错误");
        }
        this.start = start;
        this.end = end;
    }

    @Override
    public String toString() {

        return "开始时间:" + start + " , 结束时间:" + end;
    }

    /**
     * 序列化外围类时,虚拟机会转掉这个方法,序列化了一个内部的代理类对象!
     */
    private Object writeReplace() {
        System.out.println(" writeReplace()方法!");
        return new SerializabtionProxy(this);
    }


    private void readObject(ObjectInputStream ois) throws InvalidObjectException {

        throw new InvalidObjectException("Proxy request!");
    }

    /**
     * 序列化代理类,序列化时会将这个内部类进行序列化!
     */
    private static class SerializabtionProxy implements Serializable {

        private static final long serialVersionUID = 1L;
        private final Date start;
        private final Date end;

        SerializabtionProxy(Period p) {

            this.start = p.start;
            this.end = p.end;
        }

        /**
         * 反序列化这个类时,虚拟机会调用这个方法
         */
        private Object readResolve() {
            System.out.println("进入SerializabtionProxy 的 readResolve()方法,将返回Period对象!");
            // 这里进行保护性拷贝!
            return new Period(new Date(start.getTime()), new Date(end.getTime()));
        }

    }
}

序列化Period时, 会调用调用 writeReplace() 生成一个 SerializabtionProxy 对象, 然后对此对象进行序列化 ,注意,不是对Period类对象进行序列化, 而是 SerializabtionProxy对象,因为我们编写了 writeReplace() 方法,同时也编写了 readObject() 方法,并在里面抛出了一个异常,所以如果有恶意的字节流攻击时,就会执行 readObject(),并马上抛出异常来保护程序;而我们自己执行序列化时,则会执行 writeReplace() 方法,而不会执行 readObject() 方法,所以就保证了安全性。

反序列化时, 会调用 SerializabtionProxy 的 readResolve() 方法生成一个 Period 对象, 然后返回此对象的保护性拷贝的类,由此,就得到了反序列化后的 Period 对象。由此可以见,Period 的序列化和反序列化都是通过内部的代理类实现的,并非直接把自身给序列化的。

下面写个代码演示一下。

    private static void test() {
        try {
            Period period = new Period(new Date(), new Date());
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("E:/test.txt"));
            out.writeObject(period);
            out.close();

            ObjectInputStream in = new ObjectInputStream(new FileInputStream("E:/test.txt"));
            Period period2 = (Period) in.readObject();
            in.close();

            System.out.println("序列化前后是否是同一个对象  " + (period == period2));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

打印出来的日志为

 writeReplace()方法!
进入SerializabtionProxy 的 readResolve()方法,将返回Period对象!
序列化前后是否是同一个对象  false

再看看序列化的文本里面的内容

 sr Bcom.example.cn.desigin.utils.ObjectTest$Period$SerializabtionProxy       L endt Ljava/util/Date;L startq ~ xpsr java.util.Datehj?KYt  xpw  g黪鲌xsq ~ w  g黪鲌x

发现对应的上,此方法可行。


序列化代理模式有两个局限性。它不能与可以被客户端扩展的类兼容。它也不能与对象图中包含循环的某些类兼容:如果你企图从一个对象的序列化代理的readResolve方法内部调用这个对象中的方法,就会得到一个ClassCastException异常,因为你还没有这个对象,只有它的序列化代理。这种模式可用,但要遵守它的规则。

猜你喜欢

转载自blog.csdn.net/Deaht_Huimie/article/details/85336317