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 接口,那它将不再是单例:

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

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

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

     private Object readResolve() {
return INSTANCE;
}

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

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

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

  * 序列化代理模式:

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

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

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

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

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

  完整代码如下:

 import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Date; /**
* @author Haoye
* @brief
* @detail
* @date 2018/10/6
* @see
*/
public class Period implements Serializable {
private static final long serialVersionUID = 1;
private final Date start;
private final Date end; public Period(Date start, Date end) {
this.start = start;
this.end = end;
} public Date getStart() {
return start;
} public Date getEnd() {
return end;
} /**
* writeReplace for the serialization proxy pattern
* @return instance of SerializationProxy
*/
private Object writeReplace() {
return new SerializationProxy(this);
} private void readObject(ObjectInputStream inputStream) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
} /**
* proxy class
*/
private static class SerializationProxy implements Serializable{
private static final long serialVersionUID = 1;
private final Date start;
private final Date end; SerializationProxy(Period period) {
this.start = period.start;
this.end = period.end;
} private Object readResolve() {
return new Period(start, end);
}
} }

  

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

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

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

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

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

  EnumSet序列化代理类代码:

    /**
* This class is used to serialize all EnumSet instances, regardless of
* implementation type. It captures their "logical contents" and they
* are reconstructed using public static factories. This is necessary
* to ensure that the existence of a particular implementation type is
* an implementation detail.
*
* @serial include
*/
private static class SerializationProxy <E extends Enum<E>>
implements java.io.Serializable
{
/**
* The element type of this enum set.
*
* @serial
*/
private final Class<E> elementType; /**
* The elements contained in this enum set.
*
* @serial
*/
private final Enum<?>[] elements; SerializationProxy(EnumSet<E> set) {
elementType = set.elementType;
elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY);
} // instead of cast to E, we should perhaps use elementType.cast()
// to avoid injection of forged stream, but it will slow the implementation
@SuppressWarnings("unchecked")
private Object readResolve() {
EnumSet<E> result = EnumSet.noneOf(elementType);
for (Enum<?> e : elements)
result.add((E)e);
return result;
} private static final long serialVersionUID = 362491234563181265L;
}

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

《Effective Java》学习笔记 —— 序列化的更多相关文章

  1. Effective Java 学习笔记之第七条——避免使用终结(finalizer)方法

    避免使用终结方法(finalizer) 终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的. 不要把finalizer当成C++中析构函数的对应物.java中,当对象不 ...

  2. Effective Java学习笔记

    创建和销毁对象 第一条:考虑用静态工厂方法替代构造器 For example: public static Boolean valueOf(boolean b){ return b ? Boolean ...

  3. Effective Java 学习笔记之创建和销毁对象

    一.考虑用静态工厂方法代替构造器 1.此处的静态工厂方法是指返回指为类的对象的静态方法,而不是设计模式中的静态工厂方法. 2.静态工厂方法的优势有: a.使用不同的方法名称可显著地表明两个静态工厂方法 ...

  4. Java学习笔记——序列化和反序列化

    寒雨连江夜入吴,平明送客楚山孤. 洛阳亲友如相问,一片冰心在玉壶. --芙蓉楼送辛渐 持久化数据的第一种方式.在序列化之前也可以把数据打散逐行存储在文件中,然后在逐行读取. 比如定Student类 用 ...

  5. Effective Java 学习笔记之所有对象都通用的方法

    一.覆盖equals时请遵守通用约定 1.满足下列任何一个条件时,不需要覆盖equals方法 a.类的每个实例本质上都是唯一的.此时就是Object中equals方法所表达的含义. b.不关心类是否提 ...

  6. Effective Java 学习笔记----第7章 通用程序设计

    第7章 通用程序设计 第29条 将局部变量的作用域最小化     使一个局部变量的作用域最小化,最有力的技术室在第一次使用它的地方声明.   第30条 了解和使用库      效率提高.如果你不知道库 ...

  7. effective java学习笔记之不可实例化的类

    在没有显式声明一个类的构造方法时,编译器会生成默认的无参构造方法,在设计工具类时,我们通常将方法设置成静态方法,以类名.方法名的形式调用,此时这个类就没有必要创建实例,我们知道抽象类不可以被实例化,但 ...

  8. Effective Java学习笔记--创建和销毁对象

    创建和销毁对象 一.静态工厂方法代替构造器 静态工厂方法的优缺点 优点: 1.可以自定义名称(可以将功能表述的更加清晰) 2.不必每次调用都创建新的对象(同一对象重复使用) 3.返回的类型可以是原返回 ...

  9. Java学习笔记4

    Java学习笔记4 1. JDK.JRE和JVM分别是什么,区别是什么? 答: ①.JDK 是整个Java的核心,包括了Java运行环境.Java工具和Java基础类库. ②.JRE(Java Run ...

  10. java学习笔记10--枚举

    java学习笔记10--枚举 在JDK1.5之前,java可以有两种方式定义新类型:类和接口.对于大部分面向对 象编程来说,这两种方法看起来似乎足够了,但是在一些特殊情况下,这些方法就不适合.例如,想 ...

随机推荐

  1. leveldb源码分析--SSTable之TableBuilder

    上一篇文章讲述了SSTable的格式以后,本文结合源码解析SSTable是如何生成的. void TableBuilder::Add(const Slice& key, const Slice ...

  2. VScode开发Vue项目,关闭eslint代码检查,以及相关配置

    Vue初始化项目时如果不小心安装了js 语法检测 功能,撸码时一个空格不对就会各种报错 个人感觉这个语法检测功能很有点过于严格,用起来十分难受,所以果断关闭eslint,找到webpack.base. ...

  3. excel如何冻结首行或首列及首行首列同时冻结

    冻结首行方法: 首先选择首行,在菜单栏选择视图菜单,再选择冻结窗格下拉三角里的冻结首行即可. 效果如下:拖动垂直滚动条 冻结首列方法: 首先选择首列,在菜单栏选择视图菜单,再选择冻结窗格下拉三角里的冻 ...

  4. [转]Java学习---7大经典的排序算法总结实现

    [原文]https://www.toutiao.com/i6591634652274885128/ 常见排序算法总结与实现 本文使用Java实现这几种排序. 以下是对排序算法总体的介绍. 冒泡排序 比 ...

  5. 一、Ajax 二、JSON数据格式 三、Ajax+Jquery 四、分页的实现

    一.Ajax概述###<1>概述 ###<2>组成 以XMLHttpRequest为核心,发送Ajax请求和接收处理结果 以javascript为语言基础 以XML/JSON作 ...

  6. 【9】python关于os模块与os.path的相关操作

    ---恢复内容开始--- #__author:"吉*佳" #date: 2018/10/20 0020 #function: # os模块知识点 import os # 获取平台名 ...

  7. Django商城项目笔记No.14用户部分-用户中心邮箱绑定

    保存邮箱界面如下 接口设计如下 视图逻辑: 因为url是不接受pk参数的,所以UpdateApiView无法确定要更新哪个模型类,所以要重写get_object,告诉他更新哪个模型类.这里更新的是us ...

  8. Redis系列六:redis相关功能

    一. 慢查询原因分析 与mysql一样:当执行时间超过阀值,会将发生时间耗时的命令记录 redis命令生命周期:发送 排队 执行 返回慢查询只统计第3个执行步骤的时间 预设阀值:两种方式,默认为10毫 ...

  9. BZOJ2744:[HEOI2012]朋友圈(最大团,乱搞)

    Description 在很久很久以前,曾经有两个国家和睦相处,无忧无虑的生活着.一年一度的评比大会开始了,作为和平的两国,一个朋友圈数量最多的永远都是最值得他人的尊敬,所以现在就是需要你求朋友圈的最 ...

  10. 不要以为字段以transient修饰的话就一定不会被序列化

    1: 先阅读这边文章:http://www.importnew.com/21517.html 2:被transient修饰真的会被序列化吗? 反例:java.util.ArrayList中底层存储数组 ...