第十二章 序列化

目录

  • 其他替代方式优于Java本身序列化
  • 非常谨慎地实现SERIALIZABLE接口
  • 考虑使用自定义序列化形式
  • 防御性地编写READOBJECT方法
  • 对于实例控制,枚举类型优于READRESOLVE
  • 考虑序列化代理替代序列化实例

序列化

其他替代方式优于Java本身序列化

  • 没有理由在任何新系统中使用 Java 序列化。Java序列化并不安全,而且耗时
  • Jackson

非常谨慎地实现 Serializable

  1. 实现 Serializable 的一个主要成本是,它降低了一旦类发布后更改其实现的灵活性。如果接受默认的序列化形式,日后更改类的内部表示,则会导致序列化形式中的不兼容更改。 尝试使用旧版本的类序列化实例并使用新版本对其进行反序列化(反之亦然)的客户端将遇到程序失败。限制类的序列化演变的一个简单示例涉及到流的唯一标识符(stream unique identifiers),通常称为序列版本UID(serial version UIDs)。 每个可序列化的类都有一个与之关联的唯一标识号。 如果未通过声明名为serialVersionUID的静态fianl的long类型的来指定此数字,则系统会在运行时通过加密哈希函数(SHA-1)根据类的结构来自动生成它。
  2. 实现 Serializable 接口的第二个代价是,增加了出现 bug 和安全漏洞的可能性
  3. 实现 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 方法时,请考虑序列化代理模式。
    要想稳健地将带有重要约束条件的对象序列化时,这种模式可能是最容易的方法

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。