Effective Java 第三版—— 90.考虑序列化代理替代序列化实例
Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。
90. 考虑序列化代理替代序列化实例
正如在条目 85和 条目86中提到并贯穿本章的讨论,实现Serializable接口的决定,增加了出现bug和安全问题的可能性,因为它允许使用一种语言之外的机制来创建实例,而不是使用普通的构造方法。然而,有一种技术可以大大降低这些风险。这种技术称为序列化代理模式(serialization proxy pattern)。
序列化代理模式相当简单。首先,设计一个私有静态嵌套类,它简洁地表示外围类实例的逻辑状态。这个嵌套类称为外围类的序列化代理。它应该有一个构造方法,其参数类型是外围类。这个构造方法只是从它的参数拷贝数据:它不需要做任何一致性检查或防御性拷贝。按照设计,序列化代理的默认序列化形式是外围类的最好的序列化形式。外围类及其序列化代理都必须声明以实现Serializable。
例如,考虑在条目 50中编写的不可变Period类,并在条目 88中进行序列化。以下是该类的序列化代理。 Period非常简单,其序列化代理与该属性具有完全相同的属性:
// Serialization proxy for Period class
private static class SerializationProxy implements Serializable {
private final Date start;
private final Date end;
SerializationProxy(Period p) {
this.start = p.start;
this.end = p.end;
}
private static final long serialVersionUID =
234098243823485285L; // Any number will do (Item 87)
}
接下来,将以下writeReplace方法添加到外围类中。可以将此方法逐字复制到具有序列化代理的任何类中:
// writeReplace method for the serialization proxy pattern
private Object writeReplace() {
return new SerializationProxy(this);
}
该方法在外围类上的存在,导致序列化系统发出SerializationProxy实例,而不是外围类的实例。换句话说,writeReplace方法在序列化之前将外围类的实例转换为它的序列化代理。
使用此writeReplace方法,序列化系统永远不会生成外围类的序列化实例,但攻击者可能会构造一个实例,试图违反类的不变性。 要确保此类攻击失败,只需把readObject方法添加到外围类中:
// readObject method for the serialization proxy pattern
private void readObject(ObjectInputStream stream)
throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
最后,在SerializationProxy类上提供一个readResolve方法,该方法返回外围类逻辑等效的实例。此方法的存在导致序列化系统在反序列化时把序列化代理转换回外围类的实例。
这个readResolve方法只使用其公共API创建了一个外围类的实例,这就是该模式的美妙之处。它在很大程度上消除了序列化的语言外特性,因为反序列化实例是使用与任何其他实例相同的构造方法、静态工厂和方法创建的。这使你不必单独确保反序列化的实例遵从类的不变量。如果类的静态工厂或构造方法确立了这些不变性,而它的实例方法维护它们,那么就确保了这些不变性也将通过序列化来维护。
以下是Period.SerializationProxy
的readResolve
方法:
// readResolve method for Period.SerializationProxy
private Object readResolve() {
return new Period(start, end); // Uses public constructor
}
与防御性拷贝方法(第357页)一样,序列化代理方法可以阻止伪造的字节流攻击(条目 88,第354页)和内部属性盗用攻击(条目 88, 第356页)。 与前两种方法不同,这一方法允许Period
类的属性为final,这是Period类成为真正不可变所必需的(条目 17)。 与之前的两种方法不同,这个方法并没有涉及很多想法。 不你必弄清楚哪些属性可能会被狡猾的序列化攻击所破坏,也不必显示地进行有效性检查,作为反序列化的一部分。
还有另一种方法,序列化代理模式比readObject中的防御性拷贝更为强大。 序列化代理模式允许反序列化实例具有与最初序列化实例不同的类。 你可能认为这在实践中没有有用,但并非如此。
考虑EnumSet
类的情况(条目 36)。 这个类没有公共构造方法,只有静态工厂。 从客户端的角度来看,它们返回EnumSet实例,但在当前的OpenJDK实现中,它们返回两个子类中的一个,具体取决于底层枚举类型的大小。 如果底层枚举类型包含64个或更少的元素,则静态工厂返回RegularEnumSet
; 否则,他们返回一个JumboEnumSet
。
现在考虑,如果你序列化一个枚举集合,集合枚举类型有60个元素,然后将五个元素添加到这个枚举类型,再反序列化枚举集合。序列化时,这是一个RegularEnumSet
实例,但一旦反序列化,最好是JumboEnumSet
实例。事实上正是这样,因为EnumSet
使用序列化代理模式。如果好奇,如下是EnumSet
的序列化代理。其实很简单:
// EnumSet's serialization proxy
private static class SerializationProxy <E extends Enum<E>>
implements Serializable {
// The element type of this enum set.
private final Class<E> elementType;
// The elements contained in this enum set.
private final Enum<?>[] elements;
SerializationProxy(EnumSet<E> set) {
elementType = set.elementType;
elements = set.toArray(new Enum<?>[0]);
}
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;
}
序列化代理模式有两个限制。它与用户可扩展的类不兼容(条目 19)。而且,它与一些对象图包含循环的类不兼容:如果试图从对象的序列化代理的readResolve方法中调用对象上的方法,得到一个ClassCastException
异常,因为你还没有对象,只有该对象的序列化代理。
最后,序列化代理模式增强的功能和安全性并不是免费的。 在我的机器上,使用序列化代理序列化和反序列化Period实例,比使用防御性拷贝多出14%的昂贵开销。
总之,只要发现自己必须在不能由客户端扩展的类上编写readObject或writeObject方法时,请考虑序列化代理模式。 使用重要不变性来健壮序列化对象时,这种模式可能是最简单方法。
Effective Java 第三版—— 90.考虑序列化代理替代序列化实例的更多相关文章
- Effective Java 第三版——34. 使用枚举类型替代整型常量
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- 《Effective Java 第三版》目录汇总
经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- Effective Java 第三版—— 87. 考虑使用自定义序列化形式
Tips 书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code 注意,书中的有些代码里方法是基于Java 9 API中的,所 ...
- Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java第三版(一) ——用静态工厂代替构造器
此文做为<Effective Java>系列的第一篇,所以有必要大概说下此书的特点,当然很多人可能都看过,毕竟是有着Java四大名著之一的大名在外,不过总会有萌新不了解,例如我!<E ...
- effective java(第三版)---读书笔记
第一章 引言 < Effective Java>这本书并不厚,而且并不适合初学者,适合有一定的工作经验的java攻城狮.这本书不是百科全书式的JAVA 手册,而是试图在讲述如何正确.高效地 ...
- Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- HDU-2177 取(2堆)石子游戏 (威佐夫博奕)
Problem Description 有两堆石子,数量任意,可以不同.游戏开始由两个人轮流取石子.游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子:二是可以在两堆中同时取走相同 ...
- Codeforces 920F - SUM and REPLACE 【线段树】
<题目链接> 题目大意: 给你一个序列,有两个操作,一个是求区间 l - r 的和,另一个是对区间l-r的元素修改值,x=d(x),d(x)为x的因子个数. 解题分析: 因为可能有多次修改 ...
- 改变字体大小实现自适应之js方案A
一.元素大小有两种写法 1.写结果:设计师给的移动端页面sketch设计稿一般是750px宽度,在sublime编辑器里,设置cssrem或rem-unit插件为56px的字体大小.做页面时,设计稿是 ...
- 蓝桥杯 ——无重复组合——C++
问题描述: 求任意长度的(ABCD.....)全部n=0,1,2 ....组合情况(n小于等于串的长度) 解决思路: 如果串的长度固定,n的值固定循环嵌套 在这里以深度优先形成一棵树,如下图: 源代码 ...
- 2160 母猪的故事 ACM 数学规律
题目:http://acm.hdu.edu.cn/showproblem.php?pid=2160 中文题目,很简单,找规律就好. 自己画树状图找规律,开始想复杂了,找的规律:Fn=2*F(n-1)- ...
- Hibernate的核心接口
Hibernate5个核心接口 所有Hibernate应用中都会访问Hibernate的5个核心接口 Configuration接口:配置Hibernate,根启动Hibernate,创建Sessio ...
- Js全等和等于
Javascript只有六个假值(用在条件if的判断) 全等于 类型不同,返回false类型相同,则 等于 类型相同:同上=== 类型不同:尝试类型转换==[不是真值和假值的比较] null == u ...
- 2017.08.15【NOIP提高组】模拟赛B组
Summary 今天比赛很差很差,掉到谷底.第一题快排打错了,漏了递归,变成一个while循环.最后一题k忘记减一,答案一直是无穷大,所以没交.第三题没时间调DP就打了个递归,第二题状态想歪了.四道题 ...
- tarjan求双联通分量(割点,割边)
之前一直对tarjan算法的这几种不同应用比较混淆...我太弱啦! 被BLO暴虐滚过来 用tarjan求点双,很多神犇都给出了比较详细的解释和证明,在这里就不讲了(其实是这只蒟蒻根本不会orz) 这里 ...
- Helm: Error: no available release name found
如题,helm报这个错误 Helm: Error: no available release name found 错误的原因大概是因为 tiller没有正确的角色权限. 执行以下命令可解决这个问题. ...