《Effective Java》阅读笔记-第十二章
Effective Java 阅读笔记
第十二章 序列化
第 85 条 其他方法优先于 Java 本身的序列化
Java 本身的序列化漏洞过多,很容易被攻击。避免被序列化攻击的最好方式就是不要反序列化任何字节流,并且新的系统中没有任何理由使用 Java 本身的序列化。
JSON 和 Protobuf 是两种优秀的序列化方式,前者是基于文本, 后者是二进制的。
第 86 条 谨慎地实现 Serializable 接口
实现 Serializable 接口很容易,但是长期开销非常高。
- 一旦这个类被发布,就大大降低了“改变这个类的实现”的灵活性。实现 Serializable 接口代表这个类可以被转换成字节流,而内部私有字段和包级别变量也会被转换,因此就不能轻易修改实现。
- 增加了 bug 和安全漏洞出现的可能。反序列化机制就是一个“隐藏的构造器”,可以很容易的使对象关系遭到破坏。
- 随着类的更新,相关测试负担会增加。比如需要检查是否可以“在新版本中序列化一个实例,在旧版本中反序列化”。这种类越多,发行的版本越多,需要进行的测试也就越多。
因此实现 Serializable 接口不能轻易做决定。
为了继承而设计的类应尽可能少的实现 Serializable,接口也是一样。
内部类(非静态成员类)不应该实现 Serializable 接口,因为这种类包含一个指向外围实例的引用,其序列化方式是不清楚的。
静态成员类可以实现。
第 87 条 考虑使用自定义的序列化形式
如果事先没有认真考虑默认的序列化形式是否合适,那就不要贸然使用默认的方式。一旦使用了默认的序列化形式,这个类就会永远被它所牵制。
如果一个对象的物理表示等同于它的逻辑内容,可能会适合用默认的序列化形式(比如 Entity、DTO等)。
即使默认的序列化方式合适,一个也需要提供一个 readObject 的方法以保证约束关系和安全性。
当一个对象的物理表示和逻辑有实质性区别时,默认序列化有以下 4 个缺点:
- 使这个类的导出 API 永远束缚在内部实现上。(修改内部实现会导致反序列化失败)
- 消耗过多空间。默认的序列化记录了非常多的信息。
- 消耗过多时间。序列化逻辑不了解对象图的拓扑关系,所以必须要经历一个昂贵的图遍历过程。(说白了就是慢)
- 会引起栈溢出。对象图的遍历执行的方式是递归,很容易栈溢出。
第 88 条 保护性的编写 readObject 方法
第 89 条 对实例的控制,枚举类型优先于 readResolve
第 90 条 考虑使用序列化代理替换序列化实例
序列化这张没啥意思,主要就是说尽量别用 Java 自身的序列化。