使用 `readResolve` 防止序列化破坏单例模式

2024-09-07 16:36

本文主要是介绍使用 `readResolve` 防止序列化破坏单例模式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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


1. 序列化和反序列化

序列化 是指将对象的状态转换为字节流,以便存储或传输;反序列化 则是将字节流恢复为对象的过程。

当一个单例对象被序列化并随后反序列化时,反序列化过程会创建一个新的对象。由于反序列化是通过从字节流恢复对象的属性状态,而不是通过调用构造方法,这导致反序列化后的对象与原始的单例实例不同。因此,反序列化过程实际上破坏了单例模式的约束。

2. 序列化如何破坏单例模式

假设我们有一个单例类如下:

import java.io.*;public class Singleton implements Serializable {private static final Singleton INSTANCE = new Singleton();private Singleton() {// 防止通过反射破解if (INSTANCE != null) {throw new RuntimeException("单例模式禁止反射调用!");}}public static Singleton getInstance() {return INSTANCE;}
}

现在让我们通过序列化和反序列化来测试这个单例类:

public class Main {public static void main(String[] args) throws Exception {Singleton instance1 = Singleton.getInstance();// 序列化对象ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("singleton.ser"));out.writeObject(instance1);out.close();// 反序列化对象ObjectInputStream in = new ObjectInputStream(new FileInputStream("singleton.ser"));Singleton instance2 = (Singleton) in.readObject();in.close();// 比较两个实例System.out.println(instance1 == instance2);  // 输出:false}
}

问题:反序列化出来的对象 instance2 与原单例对象 instance1 是不同的实例。这显然破坏了单例模式的唯一性。


3. 使用 readResolve 防止破坏

为了解决序列化对单例模式的破坏问题,Java 提供了 readResolve 方法。在反序列化时,如果类定义了 readResolve 方法,Java 会调用这个方法,并用该方法的返回值替换反序列化生成的新对象。这意味着我们可以通过 readResolve 返回已经存在的单例对象,从而防止反序列化创建新对象。

我们可以在 Singleton 类中添加 readResolve 方法:

private Object readResolve() {return INSTANCE;  // 返回当前已存在的单例实例
}
反序列化的执行流程:
  1. 反序列化时会创建一个新的对象。
  2. 在对象完全反序列化后,Java 会检查是否存在 readResolve 方法。如果有,则调用该方法。
  3. 该方法的返回值将替换反序列化生成的对象,从而确保仍然是同一个单例对象。

通过加入 readResolve 方法,程序的输出会变成:

System.out.println(instance1 == instance2);  // 输出:true
完整代码示例:
import java.io.*;public class Singleton implements Serializable {private static final Singleton INSTANCE = new Singleton();private Singleton() {if (INSTANCE != null) {throw new RuntimeException("单例模式禁止反射调用!");}}public static Singleton getInstance() {return INSTANCE;}// readResolve 方法防止反序列化破坏单例private Object readResolve() {return INSTANCE;  // 返回当前已存在的单例实例}
}

4. 为什么 readResolve 有效

readResolve 能防止序列化破坏单例的根本原因在于它的特殊调用机制。Java 在反序列化完成后自动调用类中的 readResolve,允许我们返回一个已有的实例,从而避免生成新的对象。在单例模式中,我们可以让 readResolve 方法返回类的单例对象 INSTANCE,这样即使经历序列化和反序列化,最终得到的对象仍然是同一个单例实例。

5. 其他注意事项

  • 防止反射破坏单例:尽管 readResolve 可以防止序列化破坏单例,但反射仍然能够通过调用私有构造方法来破坏单例。因此,建议在构造方法中添加逻辑,防止反射调用,如上文所示的 if (INSTANCE != null) 检查。

  • 序列化破坏的影响:当系统中有需要频繁序列化与反序列化的单例对象时,务必要考虑使用 readResolve 来确保单例模式的完整性,否则可能会导致多个实例并存,破坏系统设计。


6. 总结

在 Java 的单例模式中,序列化和反序列化可能会破坏单例的唯一性,而通过 readResolve 方法可以有效防止反序列化生成新对象,从而维护单例模式的约束。使用 readResolve 可以确保反序列化时返回的是现有的单例实例,而不是新的对象。对于需要持久化的单例类来说,这是一个非常重要的防御措施。

确保你的单例模式在序列化和反射攻击下都具备防御机制,才能真正做到一个类的实例唯一。

⚠️:搞来搞去都不太行,要么被反射破坏、要么被序列化破坏,都得自己写代码进行解决,那么有什么可以直接用的单例模式的实现方式呢?还真有,JVM已经给我们准备好了:枚举实现序列化

这篇关于使用 `readResolve` 防止序列化破坏单例模式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145579

相关文章

使用Python实现图像LBP特征提取的操作方法

《使用Python实现图像LBP特征提取的操作方法》LBP特征叫做局部二值模式,常用于纹理特征提取,并在纹理分类中具有较强的区分能力,本文给大家介绍了如何使用Python实现图像LBP特征提取的操作方... 目录一、LBP特征介绍二、LBP特征描述三、一些改进版本的LBP1.圆形LBP算子2.旋转不变的LB

Maven的使用和配置国内源的保姆级教程

《Maven的使用和配置国内源的保姆级教程》Maven是⼀个项目管理工具,基于POM(ProjectObjectModel,项目对象模型)的概念,Maven可以通过一小段描述信息来管理项目的构建,报告... 目录1. 什么是Maven?2.创建⼀个Maven项目3.Maven 核心功能4.使用Maven H

Python中__init__方法使用的深度解析

《Python中__init__方法使用的深度解析》在Python的面向对象编程(OOP)体系中,__init__方法如同建造房屋时的奠基仪式——它定义了对象诞生时的初始状态,下面我们就来深入了解下_... 目录一、__init__的基因图谱二、初始化过程的魔法时刻继承链中的初始化顺序self参数的奥秘默认

SpringBoot使用GZIP压缩反回数据问题

《SpringBoot使用GZIP压缩反回数据问题》:本文主要介绍SpringBoot使用GZIP压缩反回数据问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot使用GZIP压缩反回数据1、初识gzip2、gzip是什么,可以干什么?3、Spr

Spring Boot 集成 Quartz并使用Cron 表达式实现定时任务

《SpringBoot集成Quartz并使用Cron表达式实现定时任务》本篇文章介绍了如何在SpringBoot中集成Quartz进行定时任务调度,并通过Cron表达式控制任务... 目录前言1. 添加 Quartz 依赖2. 创建 Quartz 任务3. 配置 Quartz 任务调度4. 启动 Sprin

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

使用Python实现一个优雅的异步定时器

《使用Python实现一个优雅的异步定时器》在Python中实现定时器功能是一个常见需求,尤其是在需要周期性执行任务的场景下,本文给大家介绍了基于asyncio和threading模块,可扩展的异步定... 目录需求背景代码1. 单例事件循环的实现2. 事件循环的运行与关闭3. 定时器核心逻辑4. 启动与停

如何使用Nginx配置将80端口重定向到443端口

《如何使用Nginx配置将80端口重定向到443端口》这篇文章主要为大家详细介绍了如何将Nginx配置为将HTTP(80端口)请求重定向到HTTPS(443端口),文中的示例代码讲解详细,有需要的小伙... 目录1. 创建或编辑Nginx配置文件2. 配置HTTP重定向到HTTPS3. 配置HTTPS服务器

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm