【如何破坏单例模式(详解)】

在这里插入图片描述

✅如何破坏单例模式

  • 💡典型解析
  • ✅拓展知识仓
    • ✅反射破坏单例
    • ✅反序列化破坏单例
    • ✅ObjectlnputStream
  • ✅总结
    • ✅如何避免单例被破坏
      • ✅ 避免反射破坏单例
      • ✅ 避免反序列化破坏单例

💡典型解析


单例模式主要是通过把一个类的构造方法私有化,来避免重复创建多个对象的。那么,想要破坏单例,只要想办注能够执行到这个私有的构造方法就行了。


✅拓展知识仓


一般来说做法有使用反射及使用反序列化都可以破坏单例。


我们先通过双重校验锁的方式创建一个单例,后文会通过反射及反序列化的方式尝试破坏这个单例。


package com.yangxiaoyuan;import java.io.Serializable;/*** Created by yangxiaoyuan on 23/12/24* 使用双重校验锁方式实现单例
*/public class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton () {}public static Singleton getsingleton()  {if (singleton == null) {synchronized (Singleton.class)  {if (singleton == null) {singleton = new Singleton();}}}return singleton;}	
}

✅反射破坏单例


我们尝试通过反射技术,来破坏单例:


Singleton singleton1 = Singleton.getSingleton();//通过反射获取到构造函数
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
//将构造函数设置为可访问类型
constructor.setAccessible(true);
//调用构造函数的newInstance创建一个对象
Singleton singleton2 = constructor.newInstance();
//判断反射创建的对象和之前的对象是不是同一个对象
System.out.println(s1 == s2);

以上代码,输出结果为false,也就是说通过反射技术,我们给单例对象创建出来了一个 "兄弟” 。


setAccessible(true),使得反射对象在使用时应该取消Java 语言访检查,使得私有的构造函数能够被访问。


✅反序列化破坏单例


我们尝试通过序列化+反序列化来破坏一下单例:


package com.yangxiaoyuan;import java.io.*;public class SerializableDemo1 {//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记//Exception直接抛出public static void main(String  args) throws IOException, ClassNotFoundException {//Write Obj to fileObjectOutputStream oos = new ObjectOutputStream(new File0utputStream("tempFile"));oos.writeObject(Singleton.getsingleton());//Read Obi from fileFile file = new File("tempFile");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton newInstance = (Singleton) ois.readObject();//判断是否是同一个对象System.out.println(newInstance == Singleton.getSingleton());}
}//false

输出结构为false,说明:


通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。


这里,在介绍如何解决这个问题之前,我们先来深入分析一下,为什么会这样?在反序列化的过程中到底发生了什么。


✅ObjectlnputStream


对象的序列化过程通过ObjectOutputStream和ObiectlnputStream来实现的,那么带着刚刚的问题,分析一下ObjectlnputStream 的 readobject 方法执行情况到底是怎样的。


为了节省篇幅,这里给出ObiectlnputStream的 readobject 的调用栈:


在这里插入图片描述

这里看一下重点代码,readOrdinaryObject 万法的代码片段: code 3


private Object readOrdinaryObject(boolean unshared) throws IOException {//此处省略部分代码Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex)  {throw (IOException) new InvalidClassException(desc .forClass().getName(),
"unable to create instance").initCause(ex);}//此处省略部分代码if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}}return obj;
}

code 3 中主要贴出两部分代码。先分析第一部分:


code3.1


Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex)  {throw (IOException) new InvalidClassException(desc .forClass().getName(),
"unable to create instance").initCause(ex);}

这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectlnputStream的 readobject 返回的对象。


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d9179a634e2a462dafeaf5c696d1a6f7.png#pic_center在这里插入图片描述


isInstantiable: 如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。




desc.newInstance:该方法通过反射的方式新建一个对象。


然后看一下 newInstance 的源码:


public T newInstance(Object ... initargs) throws InstantiationExceptionIllegalAccessException,
IllegalArgumentExceptionInvocationTargetException {if (!override) {if (!Reflection.quickCheckMemberAccess(clazz,modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, nul1, modifiers);}}if ((clazz.getModifiers() & Modifier.ENUM) != 0) {throw new IllegalArgumentException("Cannot reflectively create enum objects");}ConstructorAccessor ca = constructorAccessor;         // read volatileif (ca == null) {ca = acquireConstructorAccessor();}@Suppresslarnings("unchecked")T inst = (T) ca.newInstance(initargs];return inst;
}

其中关键的就是 T inst = (T) ca.newInstance(initargs);这一步,这里实现的话在BootstrapConstructorAccessorlmpl中,实现如下:


public Object newInstance(Object[] args)
throws IllegalArgumentExceptionInvocationTargetException {try {return UnsafeFieldAccessorImpl.unsafe.allocateInstance(constructor.getDeclaringClass());} catch (InstantiationException e)  {throw new InvocationTargetException(e);}
}

可以看到,这里通过Java 的 Unsafe 机制来创建对象的,而不是通过调用构造函数。这意味着即使类的构造函数是私有的,反序列化仍然可以创建该类的实例,因为它不依赖于常规的构造过程。


So,到目前为止,也就可以解释,为什么序列化可以破坏单例?

答:序列化会通过Unsafe直接分配的方式来创建一个新的对象。

✅总结


在涉及到序列化的场景时,要格外注意他对单例的破坏。


✅如何避免单例被破坏


✅ 避免反射破坏单例


反射是调用默认的构造函数创建出来的,只需要我们改造下构造函数,使其在反射调用的时候识别出来对象是不是被创建过就行了:


private Singleton() {if (singleton != null) {throw new RuntimeException("单例对象只能创建一次...");}
}

✅ 避免反序列化破坏单例


解决反序列化的破坏单例,只需要我们自定义反序列化的策略就行了,就是说我们不要让他走默认逻辑一直调用至Unsafe创建对象,而是我们干预他的这个过程,干预方式就是在Singleton类中定义 readResolve ,这样就可以解决该问题:


package com.yangxiaoyuan;import java.io.Serializable;// 使用双重校验锁方式实现单例public class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton (){}public static Singleton getSingleton()  {if (singleton == null) {synchronized (Singleton.class)  {if (singleton == null) {singleton = new Singleton();}}}return singleton;}private Object readResolve() {return singleton;}
}

还是运行以下测试类


package com.yangxiaoyuan;import java.io.*;public class SerializableDemo1 {//为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记//Exception直接抛出public static void main(Stringl] args) throws IOException, ClassNotFoundException {//Write Obj to fileObjectOutputStream oos = new ObiectOutputStream(new File0utputstream("tempFile")):oos.writeObject(Singleton.getSingleton());//Read Obj from fileFile file = new File("tempFile");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton newInstance = (Singleton) ois.readObject();//判断是否是同一个对象System.out.println(newInstance == Singleton.getSingleton());}
}//true

本次输出结果为true。具体原理,我们回过头继续分析code 3中的第二段代码:


if (obj != null &&handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles .setObject(passHandle, obj = rep);}
}

hasReadResolveMethod :如果实现了serializable 或者 externalizable接口的类中包含 readResolve 则返回true。


invokeReadResolve :通过反射的方式调用要被反序列化的类的readResolve方法。


所以,原理也就清楚了,只要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可可以防止单例被破坏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/577802.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp框架——vue3+uniFilePicker+fastapi实现文件上传(搭建ai项目第二步)

文章目录 ⭐前言&#x1f496; 小程序系列文章 ⭐uni-file-picker 组件&#x1f496; 绑定事件&#x1f496; uploadFile api&#x1f496; 自定义上传 ⭐后端fastapi定义上传接口⭐uniapp开启本地请求代理devServer⭐前后端联调⭐总结⭐结束 ⭐前言 大家好&#xff0c;我是ym…

数据库原理及应用·关系数据库标准语言SQL

4.1 SQL概述 4.1.1 SQL的产生和发展 1.产生 1974年&#xff0c;SQL语言的雏形最早由美国IBM公司的Raymond F. Boyce和Donald D. Chamberlin提出 1975-1979年&#xff0c;在System R上首次实现&#xff0c;由IBM的San Jose研究室研制&#xff0c;称为SEQUEL 2.发展 1986年推…

猫头虎分享2023年12月17日博客之星候选--城市赛道博主文章数据

猫头虎分享2023年12月17日博客之星候选–城市赛道博主文章数据 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开…

udp广播的例子

以下是一个使用C语言描述广播发送和接收的简单示例&#xff1a; 发送端&#xff08;广播发送&#xff09;&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #inclu…

Linux gdisk创建GPT分区

gdisk命令工具默认将磁盘划分为GPT格式的分区&#xff1a; lsblk 查看分区 创建GPT格式的分区&#xff1a; 列出磁盘分区表&#xff1a; fdisk -l 有一个新的磁盘sdc 下面将sdc进行GPT分区 输入gdisk /dev/sdc 输入&#xff1f;查看帮助文档&#xff1a; 输入n 创建新的分…

Java@RequestParam注解和@RequestBody注解接收参数

目录 Java后端接收数据 第一章、后端不写任何注解情况下接收参数1.1&#xff09;后端不写注解postman发出get请求1.2&#xff09;后端不写注解postman发出post请求 第二章、后端写RequestParam注解接收参数2.1&#xff09;postman发出post请求2.2&#xff09;postman发出get请求…

MySQL 中的 INSERT 是怎么加锁的?

在之前的博客中&#xff0c;我写了一系列的文章&#xff0c;比较系统的学习了 MySQL 的事务、隔离级别、加锁流程以及死锁&#xff0c;我自认为对常见 SQL 语句的加锁原理已经掌握的足够了&#xff0c;但看到热心网友在评论中提出的一个问题&#xff0c;我还是彻底被问蒙了。他…

【Image】GAN的超详细解释(以及奇怪的问题)

GAN原理 工作流程 下面是生成对抗网络&#xff08;GAN&#xff09;的基本工作原理 在GAN的架构中&#xff0c;有两个关键的组件&#xff1a;生成器&#xff08;Generator&#xff09;和鉴别器&#xff08;Discriminator&#xff09;。 生成器&#xff08;Generator&#xff0…

HTML5之 夜景放烟花

参考网址 https://blog.csdn.net/Gou_Hailong/article/details/122269931 https://blog.csdn.net/u013343616/article/details/122233674 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transi…

C/C++ BM2链表内指定区间反转

文章目录 前言题目1. 解决方案一1.1 思路阐述1.2 源码 2. 解决方案二2.1 思路阐述2.2 源码 总结 前言 这题是BM1的升级版&#xff0c;不过是把完整的链表翻转变成了指定区间。 题目 描述 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转&#xff0c;要求时间复杂度 …

手机蓝牙在物联网超市中的应用

超市一站式购物已进入城市的千家万户。然而人们在选购时却采用直接翻阅商品的方式&#xff0c;既不方便又不卫生甚至大大缩短食品类商品保质期&#xff0c;也给超市商品管理造成很大难度。物联网(The Internet of things)基于射频识别(RFID)、红外感应等技术&#xff0c;把物品…

CentOS环境下Nacos2.3集成PostgreSQL

title: CentOS环境下Nacos2.3集成PostgreSQL date: 2023-12-21 19:15:00 categories: Nacos description: CentOS环境下Nacos2.3集成PostgreSQL 1. 目录 1. 目录2. 简介3. 安装部署 3.1. 部署模式3.2. 环境准备3.3. 下载安装文件3.4. PostgreSQL插件 3.4.1. 下载地址3.4.2. 结…

VScode远程连接服务器,Pycharm专业版下载及远程连接(深度学习远程篇)

Visual Code、PyCharm专业版&#xff0c;本地和远程交互。 远程连接需要用到SSH协议的技术&#xff0c;常用的代码编辑器vscode 和 pycharm都有此类功能。社区版的pycharm是免费的&#xff0c;但是社区版不支持ssh连接服务器&#xff0c;只有专业版才可以&#xff0c;需要破解…

【论文阅读】MCANet: Medical Image Segmentation with Multi-Scale Cross-Axis Attention

文章目录 摘要创新点总结实现效果总结 摘要 链接&#xff1a;https://arxiv.org/abs/2312.08866 医学图像分割是医学图像处理和计算机视觉领域的关键挑战之一。由于病变区域或器官的大小和形状各异&#xff0c;有效地捕捉多尺度信息和建立像素间的长距离依赖性至关重要。本文提…

人工智能_机器学习073_SVM支持向量机_人脸识别模型建模_预测可视化_网格搜索交叉验证最优化参数对比---人工智能工作笔记0113

接着上一节来说,可以看到我们已经找到了合适的参数,然后 我们可以看一下这里 gc.best_params_ 就可以打印出最合适的参数 然后我们把最合适串按说填入到代码中,然后进行计算,看看得分 可以看到得分,训练数据是1.0 然后测试数据得分是0.7857...对吧

2024深入评测CleanMyMac X4.14.6破解版新的功能

随着时间的推移&#xff0c;我们的Mac电脑往往会变得越来越慢&#xff0c;存储空间变得越来越紧张&#xff0c;这时候一个优秀的清理工具就显得尤为重要。作为一款备受好评的Mac清理工具&#xff0c;它能够为你的Mac带来全方位的清理和优化。在本文中&#xff0c;我们将深入评测…

【HarmonyOS开发】OpenHarmony如何实现⼀次开发,多端部署

OpenHarmony提供用户程序框架、Ability框架以及UI框架&#xff0c;能够保证开发的应用在多终端运行时保证一致性。一次开发、多端部署。 多终端软件平台API具备一致性&#xff0c;确保用户程序的运行兼容性。 HarmonyOS提供了用户程序框架、Ability框架以及UI框架&#xff0c;…

WebRTC概念

定义 一个实时通信标准 通话原理 媒体协商 在WebRTC中&#xff0c;参与视频通讯的双方必须先交换SDP信息&#xff0c;获得一个都支持的编码格式 网络协商 目的&#xff1a;找到一条相互通讯的链路 做法&#xff1a;获取外网IP地址映射&#xff0c;通过信令服务器交换“网…

华锐三维云展平台 | VR在线展览云平台提供定制化虚拟展厅制作工具

随着科技的飞速发展&#xff0c;互联网技术的不断革新&#xff0c;广州华锐互动顺应时代需求&#xff0c;开发了VR在线展览云平台&#xff0c;用户可以在平台上自主创建属于自己的3D展厅。VR在线展览云平台正改变着传统展览行业的模式&#xff0c;为参展者提供更高效、更便捷、…

Hazel macOS自动化清理

Hazel是一款在Mac平台上的自动化文件管理工具&#xff0c;它可以帮助用户自动化处理文件&#xff0c;从而提高工作效率和减少重复性任务的时间和精力。以下是Hazel软件的功能特色&#xff1a; 强大的自动化处理功能&#xff1a;Hazel可以根据用户设定的规则&#xff0c;自动执…