目录
- 其他替代方式优于Java本身序列化
- 非常谨慎地实现SERIALIZABLE接口
- 考虑使用自定义序列化形式
- 防御性地编写READOBJECT方法
- 对于实例控制,枚举类型优于READRESOLVE
- 考虑序列化代理替代序列化实例
序列化
其他替代方式优于Java本身序列化
- 没有理由在任何新系统中使用 Java 序列化。Java序列化并不安全,而且耗时
- Jackson
非常谨慎地实现 Serializable
- 实现 Serializable 的一个主要成本是,它降低了一旦类发布后更改其实现的灵活性。如果接受默认的序列化形式,日后更改类的内部表示,则会导致序列化形式中的不兼容更改。 尝试使用旧版本的类序列化实例并使用新版本对其进行反序列化(反之亦然)的客户端将遇到程序失败。限制类的序列化演变的一个简单示例涉及到流的唯一标识符(stream unique identifiers),通常称为序列版本UID(serial version UIDs)。 每个可序列化的类都有一个与之关联的唯一标识号。 如果未通过声明名为serialVersionUID的静态fianl的long类型的来指定此数字,则系统会在运行时通过加密哈希函数(SHA-1)根据类的结构来自动生成它。
- 实现 Serializable 接口的第二个代价是,增加了出现 bug 和安全漏洞的可能性
- 实现 Serializable 接口的第三个代价是,它增加了与发布类的新版本相关的测试负担
- 总而言之,不要认为实现Serializable是简单的事情。除非类只在受保护的环境中使用,在这种环境中,版本永远不必相互操作,服务器永远不会暴露于不受信任的数据,否则实现Serializable是一项严肃的承诺,应该非常谨慎。如果类允许继承,则需要更加格外小心
考虑使用自定义序列化形式
- 比如ArrayList中就是自定义,即使你认为默认的序列化形式是合适的,你通常也必须提供 readObject 方法来确保不变性和安全性
- 无论选择哪种序列化形式,都要在编写的每个可序列化类中声明显式的序列版本 UID。 这消除了序列版本 UID 成为不兼容性的潜在来源
- 只有在合理描述对象的逻辑状态时,才使用默认的序列化形式;否则,设计一个适合描述对象的自定义序列化形式。
防御性地编写 readObject 方法
1 对象引用字段必须保持私有的的类,应防御性地复制该字段中的每个对象。不可变类的可变组件属于这一类。
2 检查任何不变量,如果检查失败,则抛出 InvalidObjectException。检查动作应该跟在任何防御性复制之后。
3 如果必须在反序列化后验证整个对象图,那么使用 ObjectInputValidation 接口。
4 不要直接或间接地调用类中任何可被覆盖的方法。
- 参考下ArrayList的read,writeObject
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
对于实例控制,枚举类型优于 readResolve(防止单例被序列化破坏)
- 如果将implements Serializable添加到类的声明中,则此类将不再是单例。 类是否使用默认的序列化形式或自定义序列化形式并不重要,该类是否提供显式的readObject方法也无关紧要。 任何readObject方法,无论是显式方法还是默认方法,都会返回一个新创建的实例,该实例与在类初始化时创建的实例不同。
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }
public void leaveTheBuilding() { ... }
}
- readResolve特性允许你用另一个实例替换readObject方法 创建的实例
private Object readResolve() {
// Return the one true Elvis and let the garbage collector
// take care of the Elvis impersonator.
return INSTANCE;
}
- 此方法忽略反序列化对象,返回初始化类时创建的区分的Elvis实例。因此,Elvis实例的序列化形式不需要包含任何实际数据;所有实例属性都应该声明为transient。事实上,如果依赖readResolve方法进行实例控制,那么所有具有对象引用类型的实例属性都必须声明为transient
- 如果将可序列化的实例控制类编写为枚举,Java会保证除了声明的常量之外,不会再有有任何实例
public enum Elvis {
INSTANCE;
private String[] favoriteSongs =
{ "Hound Dog", "Heartbreak Hotel" };
public void printFavorites() {
System.out.println(Arrays.toString(favoriteSongs));
}
}
考虑以序列化代理代替序列化实例
- 当必须在客户端不可扩展的类上编写 readObject 或 writeObject 方法时,请考虑序列化代理模式。
要想稳健地将带有重要约束条件的对象序列化时,这种模式可能是最容易的方法