序列化
Item 85: Prefer alternatives to Java serialization
其他方法优先于Java序列化
使用Java中的readObject()
方法进行反序列化是非常有风险的。
其一,为反序列化提供精心编写的代码流,则攻击者可以在底层硬件中执行本机的的任意代码。
其二,当攻击者向服务发送反序列化炸弹(deserialize bomb),就能够轻易完成一次拒绝服务(Denail of Service)攻击。
避免序列化攻击的最佳方式就是永远不要反序列化任何东西。 没有理由在你写的任何程序中使用Java序列化。
如果非要使用Java序列化,使用Java 9 支持的java.io.ObjectInputFilter
。
Item 86: Implement Serializable with great caution
谨慎地实现Serializable接口。
实现Serializable
接口最大的代价就是,一旦某个类被发布,被存储在硬盘中,他可修改的灵活性就大大降低了。
如果使用了默认的序列化方式,一旦类的内部形式发生改变,那么发序列化老数据的时候,就会产生兼容性的问题。
实现序列化接口的第二个代价就是,会增加安全漏洞以及BUG的几率。通过序列化来创建对象,是一种超出语言之外的机制,很容易跳过系统的安全检查。
第三个代价是,增加了新类的测试负担。
如果一个专门为继承而生的类,和接口应当尽可能少地去实现序列化接口。内部类不应该去实现序列化接口
Item 87: Consider using a custom serialized form
考虑使用自定义的序列化形式。
在深思熟虑之前,不要贸然接受默认的序列化形式。
如果一个对象的物理表现形式和逻辑内容相同,可以考虑使用默认的序列化形式。不过,即使你确定了使用默认的序列化形式是合适的,依旧需要提供一个readObject
方法来确保约束关系和安全性。
如果将某些字段设置为transient
,那么反序列化的时候,字段值将会被初始化为默认值。如果是引用数据类型,则为null
,基本数据类型则为0
或者false
。如果不能接受的话,必须提供readObject
,在其实现内为你想要的属性去赋值。
此外,如果你在读取对象状态的其他方法上施加了同步,则也必须在序列化上强制这种同步。
一定要为实现序列化接口的类显式地声明一个序列化UID。不要去随意修改这个UID,除非你想要放弃系统的兼容性。
Item 88: Write readObject
methods defensively
保护性地编写
readObejct
方法
当一个对象被反序列化的时候,若其某个字段包含客户端不存在的引用,则对此字段的保护性拷贝是至关重要的。
可以通过一个石蕊测试来确定,你是应该使用默认的readObject
还是需要定义自己的readObject
:你觉得为所有的非transient
字段都添加到一个公有构造器,并且并施加任何参数校验是否合适?
禁止在readObject
方法中引用任何用于被重写的方法。
Item 88: For instance control, prefer enum types to readResolve
为了控制实例,枚举类型优先于
readResolve
对于任何的readObject
方法,无论是显式的还是默认的,都会返回一个新的实例,该实例不同于创建时的实例。
readResolve
的上述性质会破坏单例模式,为了保证单例的性质,可以使用枚举类对象。
事实上,如果你依赖于readResolve
控制实例,所有的字段都应该被声明为transient
的。
Item 90: Consider serialization proxies instead of seriallized instance
考虑使用序列化代理替代序列化实例
序列化模式相当简单:
- 为可序列化的类设计一个私有的静态嵌套类,精确表示外围类的逻辑状态
- 嵌套类的参数类型是外围类,并且拥有一个构造方法
- 构造器只从其参数中来复制值,而不做任何参数检查或者保护性拷贝
序列化代理有两个局限性:
- 不能与可以被客户端扩展的类相兼容
- 序列化代理模式增强的功能和安全性并不是没有代价的,保护性拷贝会降低系统的运行效率