Java设计模式系列--单例模式(破坏单例的方法)

news/2024/10/23 7:15:01/

原文网址:Java设计模式系列--单例模式(破坏单例的方法)_IT利刃出鞘的博客-CSDN博客

简介

说明

本文介绍Java破坏单例模式的方法。

单例模式(Singleton Pattern)是指确保一个类只有一个实例,并提供一个全局访问点。破坏单例模式的话,就是说可以创建出两个或两个以上的实例。本文通过静态内部类方式进行说明。

破坏单例的方法

破坏单列模式的方式

  1. 反射
  2. 序列和反序列化
  3. 使用不同的类加载器

上边1, 2都可以用枚举来解决,具体见:此文。

反射

破坏单例模式 

单例类

package org.example.a;public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

测试类

package org.example.a;import java.lang.reflect.Constructor;public class Demo {public static void main(String[] args) {Class<?> singletonClass = Singleton.class;try {Constructor c = singletonClass.getDeclaredConstructor(null);c.setAccessible(true);Object singleton1 = c.newInstance();Object singleton2 = c.newInstance();System.out.println(singleton1 == singleton2);} catch (Throwable e) {e.printStackTrace();}}
}

执行结果(创建出了两个不同的实例)

false

防止反射破坏单例模式

在构造函数里防止反射。

单例类

package org.example.a;public class Singleton {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton() {if (SingletonHolder.INSTANCE != null) {throw new RuntimeException("不允许通过反射创建实例");}}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

测试类(通过反射获得实例)

package org.example.a;import java.lang.reflect.Constructor;public class Demo {public static void main(String[] args) {Class<?> singletonClass = Singleton.class;try {Constructor c = singletonClass.getDeclaredConstructor(null);c.setAccessible(true);Object singleton1 = c.newInstance();System.out.println("singleton1 instance create successfully");Object singleton2 = c.newInstance();System.out.println("singleton2 instance create successfully");System.out.println(singleton1 == singleton2);} catch (Throwable e) {e.printStackTrace();}}
}

执行结果

Caused by: java.lang.RuntimeException: 不允许通过反射创建实例

测试类(通过静态方法获得实例)

package org.example.a;public class Demo {public static void main(String[] args) {Singleton singleton = Singleton.getInstance();System.out.println("singleton create successfully");}
}

执行结果

singleton create successfully

防止反射破坏单例模式的原理

1.用getInstance

    调用到new Singleton()时,此时INSTANCE是null,所以不会报错。

2.用反射

    调用构造方法时,调用到Singleton.INSTANCE,然后会再new Singleton()赋值给Singleton.INSTANCE,此时,Singleton.INSTANCE不为null。

序列化与反序列化

破坏单例模式

单例类

package org.example.a;import java.io.Serializable;public class Singleton implements Serializable {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

测试类

package org.example.a;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;public class Demo {public static void main(String[] args) {Singleton s1 = null;Singleton s2 = Singleton.getInstance();FileOutputStream fos = null;try {//序列化到文件中fos = new FileOutputStream("Singleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s2);oos.flush();oos.close();//从文件中反序列化为对象FileInputStream fis = new FileInputStream("Singleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s1 = (Singleton) ois.readObject();ois.close();//对比结果System.out.println(s1 == s2);} catch (Exception e) {e.printStackTrace();}}
}

执行结果(产生多个实例)

false

防止序列化/反序列化破坏单例模式

单例类

package org.example.a;import java.io.Serializable;public class Singleton implements Serializable {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}private Object readResolve() {return SingletonHolder.INSTANCE;}
}

测试类

package org.example.a;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;public class Demo {public static void main(String[] args) {Singleton s1 = null;Singleton s2 = Singleton.getInstance();FileOutputStream fos = null;try {//序列化到文件中fos = new FileOutputStream("Singleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s2);oos.flush();oos.close();//从文件中反序列化为对象FileInputStream fis = new FileInputStream("Singleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s1 = (Singleton) ois.readObject();ois.close();//对比结果System.out.println(s1 == s2);} catch (Exception e) {e.printStackTrace();}}
}

执行结果(保护了单例模式)

true

防止序列化/反序列化破坏单例模式的原理

追踪序列化源码

java.io.ObjectInputStream#readObject

readObject				//java.io.ObjectInputStreamreadObject0(false)	//java.io.ObjectInputStream。case TC_OBJECT: return checkResolve(readOrdinaryObject(unshared));readOrdinaryObject(unshared)  //java.io.ObjectInputStreamObject obj;try {//判断是否有无参的构造函数,有的话就调用newInstance()实例化对象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);//其他代码}

        关键是desc.hasReadResolveMethod() ,这段代码的意思是查看你的单例类里面有没有readResolve方法,有的话就利用反射的方式执行这个方法,具体是desc.invokeReadResolve(obj)这段代码,返回单例对象。这里其实是实例化了两次,只不过新创建的对象没有被返回而已。如果创建对象的动作发生频率增大,就意味着内存分配开销也就随之增大,这也算是一个缺点吧。

不同的类加载器

不同的类加载器破坏单例模式

单例类 

package org.example.a;import java.io.Serializable;public class Singleton implements Serializable {private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}private Singleton() {}public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}

测试类

package org.example.a;import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;public class Demo {public static void main(String[] args) throws Throwable{ClassLoader myLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {try {String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";InputStream is = getClass().getResourceAsStream(fileName);if (is == null) {return super.loadClass(name);}byte[] b = new byte[is.available()];is.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {throw new ClassNotFoundException(name);}}};// 由系统应用程序类加载器加载Singleton instance1 = Singleton.getInstance();// 自定义的类加载器加载Class<?> clazz = myLoader.loadClass("org.example.a.Singleton");Constructor<?> constructor = clazz.getDeclaredConstructor();constructor.setAccessible(true);Object instance2 = constructor.newInstance();System.out.println(instance2.getClass());System.out.println(instance2 == instance1);}
}

 运行结果

class org.example.a.Singleton
false

防止不同的类加载器破坏单例模式

没有本质上解决方法。只能说,通过代码来保证使用的是同一个加载器:

private static Class<?> getClass(String classname) throws ClassNotFoundException {ClassLoader loader = Thread.currentThread().getContextClassLoader();if (loader == null) {loader = Singleton.class.getClassLoader();}return (loader.loadClass(classname));
}


http://www.ppmy.cn/news/100777.html

相关文章

【Bug 全解决】 Java、Spring boot 后端项目 Bug 总结

Bug 收集与总结 本文记录的是 SpringBoot 后端项目使用和运行代码时所遇到的各种问题&#xff0c;全部都已解决&#xff0c;欢迎在评论区补充你遇到的 Bug 哦&#xff01;仅以本文记录学习社区项目时&#xff0c;所遇到的奇奇怪怪的 bug&#xff0c;以及一些很愚蠢的错误&…

c#快速入门

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析2 目录 &#x1f449;&#x1f3fb; c#和c不同之处&#x1f449;&#x1f3fb;程序文件的…

Flink 快速上手,实操记录

目录 Flink 快速上手安装编写 Flink 程序总结 Flink 快速上手 Apache Flink 是一个开源的流处理框架&#xff0c;它提供了高效、可扩展、分布式的数据处理能力。本文将介绍如何快速上手 Flink。 安装 Flink 可以在官网上下载二进制包&#xff0c;也可以通过 Maven 或 Gradle…

海思sdk快速上手

mpp&#xff1a;视频H.264的编码压缩 1.看linux、uboot的文档 2.移植SDK到ubuntu 2.1、三个脚本 source sdk.unpack解压 2.2、osdrv/Makefile和readme make OSDRV_CROSSarm-hisiv300-linux CHIPhi3518ev200 all报错 参考&#xff1a;ubuntu16.04 编译错误: /bin/sh: 1: pushd…

公务员等级划分

您好部级干部&#xff1a;指国家行政机关中最高级别的干部&#xff0c;他们一般负责全国性的职务&#xff0c;一般由国务院任命&#xff0c;担任全国最高政府机构的领导职务。厅级干部&#xff1a;指国家行政机关中级别较高的干部&#xff0c;他们一般负责省级的职务&#xff0…

Vue实现二维码,让你的数据轻松传递

前言 在我们生活中&#xff0c;二维码的应用越来越广泛&#xff0c;特别是在移动互联网的时代&#xff0c;二维码成为了快速传达信息的一种利器。在这篇文章中&#xff0c;我们将会介绍如何在Vue框架下&#xff0c;实现一个具备扫描和查看数据的二维码。 在这一篇文章中&…

45°装备系统

45装备系统&#xff0c;规则&#xff1a;1、45 脚后剧情&#xff0c;场景地面出现&#xff0c;个体视角&#xff0c;非群体。2、产生寒暖对立&#xff0c;衣饰自动改变。3、地图下方块蛇&#xff0c;脚步顺逆差&#xff0c;让衣饰自动改变后出现形态特效。&#xff08;形成进入…