《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 }
* 序列化代理模式的好处:
(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 }
本文地址:https://www.cnblogs.com/laishenghao/p/effective_java_note_serialization.html