Update 设计模式 - 单例.md

补充枚举单例防止反序列化破坏的原因和几种写法的选择,增加参考资料。
This commit is contained in:
CyC2018
2026-10-31 14:03:56 +08:00
parent 42f623dcaa
commit 86d92e1026
+5 -1
View File
@@ -184,7 +184,9 @@ java.lang.IllegalArgumentException: Cannot reflectively create enum objects
该实现可以防止反射攻击。在其它实现中,调用私有构造函数的 setAccessible(true) 可以绕过访问检查,再调用 newInstance() 创建新的实例;要防止这种攻击,可以在构造函数中检查实例是否已经存在并抛出异常,但这只能挡住实例创建之后的反射调用:懒汉式在第一次调用 getUniqueInstance() 之前被反射创建时,检查不出来。对于枚举类型,Constructor.newInstance() 直接抛出 IllegalArgumentException,因此不会出现上述的反射攻击。
该实现在多次序列化和反序列化之后,不会得到多个实例。而其它实现要实现 Serializable,反序列化时每次都会创建一个新对象,需要提供 readResolve() 方法返回已有的实例来替换它;transient 只决定字段是否参与序列化,不能保证实例唯一。
该实现在多次序列化和反序列化之后,不会得到多个实例。这是因为枚举常量序列化时只写入它的名字,反序列化时用 Enum.valueOf() 按名字找回已有的常量,这个过程不能自定义。而其它实现要实现 Serializable,反序列化时每次都会创建一个新对象,需要提供 readResolve() 方法返回已有的实例来替换它;transient 只决定字段是否参与序列化,不能保证实例唯一。
这几种写法中,《Effective Java》推荐单元素的枚举,它同时挡住了反射和反序列化;枚举不能继承其它类,单例需要继承某个父类时,用静态内部类实现。
### 示例
- 日志类;
@@ -206,3 +208,5 @@ Spring 默认的单例 Bean 与这里的单例不同:它指的是一个容器
- 弗里曼. Head First 设计模式 [M]. 中国电力出版社, 2007.
- Gamma E. 设计模式:可复用面向对象软件的基础 [M]. 机械工业出版社, 2007.
- Bloch J. Effective Java(第 3 版) [M]. Addison-Wesley, 2018.
- [Java Object Serialization Specification: Serialization of Enum Constants](https://docs.oracle.com/en/java/javase/21/docs/specs/serialization/serial-arch.html)