zoukankan      html  css  js  c++  java
  • 《Effective Java》学习笔记 —— 序列化

      Java的序列化API提供了一个框架,用来将对象编码成一个字节流(序列化,serializing),并从字节流中重新创建对象(反序列化, deserializing)。

    第74条 谨慎地实现Serializable接口

      * 实现Serializable接口最大的代价是,一旦一个类被发布,就大大降低了“改变这个类的实现”的灵活性。

      * 实现Serializable接口的第二个代价是,它增加了出现Bug和安全漏洞的可能性。

      * 实现Serializable接口的第三个代价是,随着新版本的发布,相关的测试负担也增加了。

      * 内部类(inner class)不应该实现Serializable接口,内部类的默认序列化形式是定义不清楚的。而静态成员类(static member class)可以实现。

    第75条 考虑使用自定义的序列化形式

      * 如果所有的实例域都是瞬时的(transient),从技术角度而言,不调用 defaultWriteObject 和 defaultReadObject 也是允许的,但不推荐这么做。

      * 被标记为transient的域,如果是对象引用域,则被初始化为null;如果是数值基本域,则为0;如果是boolean域,则为false。如果这些初始值不能被任何transient域接受,则需要提供一个readObject方法,先调用defaultReadObject,然后再将其恢复为可接受的值。

      * 声明一个显式的序列化版本id。

    第76条 保护性的编写 readObject 方法

      * readObject方法实际上如同一个公有的构造器,如同其他构造器一样,也要注意同样的所有注意事项。

      * 编写健壮的readO方法:

        -- 对于对象引用域必须保持为私有的类,要保护性的拷贝这些域中的每一个对象。

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

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

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

    第78条 对于实例控制,枚举类型优先于readResolve

      * 如果以下类实现Serializable 接口,那它将不再是单例:

    1 public class Singleon {
    2     private static final Singleon INSTANCE = new Singleon();
    3 
    4     private Singleon() { }
    5 
    6     // other codes
    7 }
    // 实现序列化接口后将不再是一个单例
    public class Singleon implements Serializable {
        private static final Singleon INSTANCE = new Singleon();
    
        private Singleon() { }
    
        // other codes
    }

      readResolve特性允许通过readObject创建的实例代替另一个实例。对于一个正在被反序列化的对象,如果它的类定义了一个readResolve方法,并且具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用。然后该方法返回的引用将被返回,取代新建的对象。

      如果上述的Singleon类实现了下面的方法,就足以保证它的单例属性:

    1     private Object readResolve() {
    2         return INSTANCE;
    3     }

      事实上,如果依赖readResolve进行实例控制,带有对象引用类型的所有实例域都必须声明为transient。否则,攻击者就有可能在readResolve方法被运行之前,保护指向反序列化的对象的引用。

      * 将一个可序列化的实例受控的类编写成枚举,则可以绝对保证除了声明的常量之外,不会有别的实例。

    第78条 考虑使用序列化代理代替序列化实例

      * 序列化代理模式:

        (1)首先,增加一个静态内部类,其成员参数与需要序列化的外部类一致。

        (2)然后,在外部类中添加writeReplace方法。修改序列化时写入的内容,写入代理类的内容,而不是外部类的。

        (3)再在外部类添加readObject方法,让其抛出异常(由于写入的时候不是外部类的,那读的时候肯定也不是外部类的,如果是,则一定是伪造的)。

        (4)最后,在内部类中增加一个readResolve 方法,返回一个外部类实例。

      从整体上看,序列化代理模式就是增加一层转换。写入时先转换为代理类再写入,读出时再将其转换为被代理的类。

      完整代码如下:

     1 import java.io.InvalidObjectException;
     2 import java.io.ObjectInputStream;
     3 import java.io.Serializable;
     4 import java.util.Date;
     5 
     6 /**
     7  * @author Haoye
     8  * @brief
     9  * @detail
    10  * @date 2018/10/6
    11  * @see
    12  */
    13 public class Period implements Serializable {
    14     private static final long serialVersionUID = 1;
    15     private final Date start;
    16     private final Date end;
    17 
    18     public Period(Date start, Date end) {
    19         this.start = start;
    20         this.end = end;
    21     }
    22 
    23     public Date getStart() {
    24         return start;
    25     }
    26 
    27     public Date getEnd() {
    28         return end;
    29     }
    30 
    31     /**
    32      * writeReplace for the serialization proxy pattern
    33      * @return instance of SerializationProxy
    34      */
    35     private Object writeReplace() {
    36         return new SerializationProxy(this);
    37     }
    38 
    39     private void readObject(ObjectInputStream inputStream) throws InvalidObjectException {
    40         throw new InvalidObjectException("Proxy required");
    41     }
    42 
    43     /**
    44      * proxy class
    45      */
    46     private static class SerializationProxy implements Serializable{
    47         private static final long serialVersionUID = 1;
    48         private final Date start;
    49         private final Date end;
    50 
    51         SerializationProxy(Period period) {
    52             this.start = period.start;
    53             this.end = period.end;
    54         }
    55 
    56         private Object readResolve() {
    57             return new Period(start, end);
    58         }
    59     }
    60 
    61 }
    View Code

      

      * 序列化代理模式的好处:  

        (1)可以阻止伪字节流的攻击。

        (2)也可以阻止内部域的盗用攻击。

        (3)并且允许实际需要序列化的类的域声明为final。

        (4)序列化代理模式允许反序列化实例与原始序列化实例有着不同的类(如EnumSet的序列化代理)。

      EnumSet序列化代理类代码:

     1    /**
     2      * This class is used to serialize all EnumSet instances, regardless of
     3      * implementation type.  It captures their "logical contents" and they
     4      * are reconstructed using public static factories.  This is necessary
     5      * to ensure that the existence of a particular implementation type is
     6      * an implementation detail.
     7      *
     8      * @serial include
     9      */
    10     private static class SerializationProxy <E extends Enum<E>>
    11         implements java.io.Serializable
    12     {
    13         /**
    14          * The element type of this enum set.
    15          *
    16          * @serial
    17          */
    18         private final Class<E> elementType;
    19 
    20         /**
    21          * The elements contained in this enum set.
    22          *
    23          * @serial
    24          */
    25         private final Enum<?>[] elements;
    26 
    27         SerializationProxy(EnumSet<E> set) {
    28             elementType = set.elementType;
    29             elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY);
    30         }
    31 
    32         // instead of cast to E, we should perhaps use elementType.cast()
    33         // to avoid injection of forged stream, but it will slow the implementation
    34         @SuppressWarnings("unchecked")
    35         private Object readResolve() {
    36             EnumSet<E> result = EnumSet.noneOf(elementType);
    37             for (Enum<?> e : elements)
    38                 result.add((E)e);
    39             return result;
    40         }
    41 
    42         private static final long serialVersionUID = 362491234563181265L;
    43     }
    View Code

    本文地址:https://www.cnblogs.com/laishenghao/p/effective_java_note_serialization.html

  • 相关阅读:
    linux开机自启动服务优化设置命令
    yum网络源配置
    CentOS 6一键系统优化 Shell 脚本
    linux系统下find删除目录下除一文件外的所有文件
    linux系统时间同步
    Memcached 查询stats及各项状态解释
    VMWARE 12安装Tools
    win8及以上2012 R2,virtualbox 5.0.20安装centOS6以上各种注意事项
    HTTP的上传文件实例分析
    java中类加载顺序(深入Java)
  • 原文地址:https://www.cnblogs.com/laishenghao/p/effective_java_note_serialization.html
Copyright © 2011-2022 走看看