跟我学(Effective Java 2)第76条:保护性地编写readObject方法

第76条:保护性地编写readObject方法

当一个对象被反序列化的时候,对于客户端不应该拥有的对象引用,如果哪个域包含了这样的对象引用,就必须要做保护性拷贝,这是非常重要的。因此,对于每个可序

列化的不可变类,如果它包含了私有的可变组件,那么在它的readObject方法中,必须要对这些组件进行保护性拷贝。

有一个简单的"石蕊"测试,可以用来确定默认的readObject方法是否可以接受。测试方法:增加一个公有的构造器,其参数对应于该对象中每个非瞬时的域,并且无论参

数的值是什么,都是不进行检查就可以保存到相应的域中的。对于这样的做法,你是否会感到很舒适?如果你对这个问题不能回答"是",就必须提供一个显式的

readObject方法,并且它必须执行构造器所要求的所有有效性检查和保护性拷贝。另一种方法是,可以使用序列化代理模式(serialization proxy pattern,见第78条

)。

public class Period implements Serializable{

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

    public Period(Date start, Date end) {
        if(this.start.compareTo(this.end) > 0){
            throw new IllegalArgumentException(start + "after" + end);
        }
        this.start = start;
        this.end = end;
    }

    public Date start(){
        return new Date(start.getTime());
    }

    public Date end(){
        return new Date(end.getTime());
    }

    @Override
    public String toString(){
        return "start:" + start + " , end:" + end;
    }

    /**
     * 序列化外围类时,调用内部的静态代理类,最后其实是序列化了一个内部的代理类的参数,所以不影响外部类的实例。
     * @return
     */
    private Object writeReplace(){
        return new SerializabtionProxy(this);
    }

    /**
     * 如果攻击者伪造了一个字节码文件,为了确保这种攻击无法得逞,只要外围类的readObject方法直接抛异常即可。
     * @param stream
     * @throws InvalidObjectException
     */
    private void readObject(ObjectInputStream stream) throws InvalidObjectException{
        throw new InvalidObjectException("Proxy required!");
    }

    /**
     * 序列化代理类,他精确表示了其当前外围类对象的状态!最后序列化时会将这个私有内部内进行序列化!
     */
    private static class SerializabtionProxy implements Serializable{


        private final Date start;
        private final Date end;
        SerializabtionProxy(Period p){

            this.start = p.start;
            this.end = p.end;
        }
        private static final long serialVersionUID = 1L;
        /**
         * 反序列化这个类时,虚拟机会调用这个方法,最后返回的对象是一个Period对象!这里同样调用了Period的构造函数,
         * 会进行构造函数的一些校验!
         */
        private Object readResolve(){
            // 这里进行保护性拷贝!
            return new Period(new Date(start.getTime()), new Date(end.getTime()));
        }

    }
}

对于非final的可序列化的类,在readObject方法和构造器之间还有其他类似的地方。readObject方法不可以调用可被覆盖的方法,无论是直接调用还是间接调用都不可

以(见第17条)。如果违反了这条规则,并且覆盖了该方法,被覆盖的方法将在子类的状态被反序列化之前先运行。程序很可能会失败。

总而言之,每当你编写readObject方法的时候,都要这样想:你正在编写一个公有的构造器,无论给它传递什么样的字节流,它都必须产生一个有效的实例。不要假设这

个字节流一定代表着一个真正被序列化过的实例。虽然在本条目的例子中,类使用了默认的序列化形式,但是,所有讨论到的有可能发生的问题也同样适用于使用自定义

序列化形式的类。下面以摘要的形式给出一些指导方针,有助于编写出更加健壮的readObject方法:

1 对于对象引用域必须保持为私有的类,要保护性地拷贝这些域中的每个对象。不可变类的可变组件就属于这一类别。

2 对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作应该跟在所有的保护性拷贝之后。

3 如果整个对象图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口。

4 无论是直接方式还是间接方式,都不要调用类中任何可被覆盖的方法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值