使用 `readResolve` 防止序列化粉碎单例模式

一给  金牌会员 | 2024-9-11 17:30:38 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 809|帖子 809|积分 2427

单例模式是一种计划模式,其目的是确保一个类只有一个实例,并提供一个全局访问点。在 Java 中,我们常常通过私有化构造方法和提供静态访问方法来实现单例。然而,尽管这些本领可以有用防止类的实例化,反射和序列化依然可以大概粉碎单例模式的唯一性。本文将重点解说序列化如何粉碎单例模式,以及如何通过 readResolve 方法来防止这种粉碎。

1. 序列化和反序列化

序列化 是指将对象的状态转换为字节流,以便存储或传输;反序列化 则是将字节流恢复为对象的过程。
当一个单例对象被序列化并随后反序列化时,反序列化过程会创建一个新的对象。由于反序列化是通过从字节流恢复对象的属性状态,而不是通过调用构造方法,这导致反序列化后的对象与原始的单例实例不同。因此,反序列化过程实际上粉碎了单例模式的约束。
2. 序列化如何粉碎单例模式

假设我们有一个单例类如下:
  1. import java.io.*;
  2. public class Singleton implements Serializable {
  3.     private static final Singleton INSTANCE = new Singleton();
  4.     private Singleton() {
  5.         // 防止通过反射破解
  6.         if (INSTANCE != null) {
  7.             throw new RuntimeException("单例模式禁止反射调用!");
  8.         }
  9.     }
  10.     public static Singleton getInstance() {
  11.         return INSTANCE;
  12.     }
  13. }
复制代码
现在让我们通过序列化和反序列化来测试这个单例类:
  1. public class Main {
  2.     public static void main(String[] args) throws Exception {
  3.         Singleton instance1 = Singleton.getInstance();
  4.         // 序列化对象
  5.         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
  6.         out.writeObject(instance1);
  7.         out.close();
  8.         // 反序列化对象
  9.         ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));
  10.         Singleton instance2 = (Singleton) in.readObject();
  11.         in.close();
  12.         // 比较两个实例
  13.         System.out.println(instance1 == instance2);  // 输出:false
  14.     }
  15. }
复制代码
问题:反序列化出来的对象 instance2 与原单例对象 instance1 是不同的实例。这显然粉碎了单例模式的唯一性。

3. 使用 readResolve 防止粉碎

为了解决序列化对单例模式的粉碎问题,Java 提供了 readResolve 方法。在反序列化时,假如类定义了 readResolve 方法,Java 会调用这个方法,并用该方法的返回值更换反序列化生成的新对象。这意味着我们可以通过 readResolve 返回已经存在的单例对象,从而防止反序列化创建新对象。
我们可以在 Singleton 类中添加 readResolve 方法:
  1. private Object readResolve() {
  2.     return INSTANCE;  // 返回当前已存在的单例实例
  3. }
复制代码
反序列化的执行流程:


  • 反序列化时会创建一个新的对象。
  • 在对象完全反序列化后,Java 会查抄是否存在 readResolve 方法。假如有,则调用该方法。
  • 该方法的返回值将更换反序列化生成的对象,从而确保仍然是同一个单例对象。
通过参加 readResolve 方法,步伐的输出会酿成:
  1. System.out.println(instance1 == instance2);  // 输出:true
复制代码
完整代码示例:

  1. import java.io.*;
  2. public class Singleton implements Serializable {
  3.     private static final Singleton INSTANCE = new Singleton();
  4.     private Singleton() {
  5.         if (INSTANCE != null) {
  6.             throw new RuntimeException("单例模式禁止反射调用!");
  7.         }
  8.     }
  9.     public static Singleton getInstance() {
  10.         return INSTANCE;
  11.     }
  12.     // readResolve 方法防止反序列化破坏单例
  13.     private Object readResolve() {
  14.         return INSTANCE;  // 返回当前已存在的单例实例
  15.     }
  16. }
复制代码

4. 为什么 readResolve 有用

readResolve 能防止序列化粉碎单例的根本原因在于它的特殊调用机制。Java 在反序列化完成后自动调用类中的 readResolve,允许我们返回一个已有的实例,从而制止生成新的对象。在单例模式中,我们可以让 readResolve 方法返回类的单例对象 INSTANCE,如许即使经历序列化和反序列化,终极得到的对象仍然是同一个单例实例。
5. 其他留意事项



  • 防止反射粉碎单例:尽管 readResolve 可以防止序列化粉碎单例,但反射仍然可以大概通过调用私有构造方法来粉碎单例。因此,发起在构造方法中添加逻辑,防止反射调用,如上文所示的 if (INSTANCE != null) 查抄。
  • 序列化粉碎的影响:当系统中有必要频繁序列化与反序列化的单例对象时,务须要思量使用 readResolve 来确保单例模式的完整性,否则大概会导致多个实例并存,粉碎系统计划。

6. 总结

在 Java 的单例模式中,序列化和反序列化大概会粉碎单例的唯一性,而通过 readResolve 方法可以有用防止反序列化生成新对象,从而维护单例模式的约束。使用 readResolve 可以确保反序列化时返回的是现有的单例实例,而不是新的对象。对于必要持久化的单例类来说,这是一个非常重要的防御措施。
确保你的单例模式在序列化和反射攻击下都具备防御机制,才能真正做到一个类的实例唯一。
⚠️:搞来搞去都不太行,要么被反射粉碎、要么被序列化粉碎,都得本身写代码举行解决,那么有什么可以直接用的单例模式的实现方式呢?还真有,JVM已经给我们准备好了:罗列实现序列化

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

一给

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表