欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

反射和反序列化破坏单例及解决方案

程序员文章站 2022-09-17 09:12:21
反射和反序列化破坏单例及解决方案上一篇介绍了几种常见的单例模式以及优缺点。一、反射破坏单例以及解决方案反射破坏单例上面介绍的单例模式的构造方法除了私有化加上 private 以外,没有做任何处理。如果我们使用反射来调用其构造方法,然后再调用 getInstance()方法,应该就会两个不同的实例。现在来看一段测试代码,以静态内部类单例为例:静态内部类单例public class LazyInnerClassSingleton {private LazyInnerClassSingleto...

反射和反序列化破坏单例及解决方案

上一篇介绍了几种常见的单例模式以及优缺点。

一、反射破坏单例以及解决方案

反射破坏单例

上面介绍的单例模式的构造方法除了私有化加上 private 以外,没有做任何处理。如果我们使用反射来调用其构造方法,然后再调用 getInstance()方法,应该就会两个不同的实例。
现在来看一段测试代码,以静态内部类单例为例:

静态内部类单例
public class LazyInnerClassSingleton {

	private LazyInnerClassSingleton(){}

	public static final LazyInnerClassSingleton getInstance() {
		return LazyHolder.LAZY;
	}

	private static class LazyHolder {
		private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
	}
}
利用反射破坏单例
public static void main(String[] args) {
    try {
        // 反射来获取对象
        Class<?> clzz = LazyInnerClassSingleton.class;
        Constructor c = clzz.getDeclaredConstructor(null);
        // 授权,因为构造方法为私有的,强吻
        c.setAccessible(true);
        // 反射获取单例
        Object o1 = c.newInstance();
        // 正常获取单例获取
        Object o2 = LazyInnerClassSingleton.getInstance();
        System.out.println(o1);
        System.out.println(o2);
        // 比较o1和o2的内存地址 结果为 false
        System.out.println(o1 == o2);
        // 即 o1和o2不是同一个对象。单例就被破坏了
    } catch (Exception e) {
        e.printStackTrace();
    }
}

结果如下:

org.about.designpatterns.singleton.lazy.LazyInnerClassSingleton@a09ee92
org.about.designpatterns.singleton.lazy.LazyInnerClassSingleton@30f39991
false

Process finished with exit code 0

解决方案

在私有构造中加判断,若是想通过使用反射来调用其构造方法,然后再调用 getInstance()方法来创建实例,就会报错。其余的单例也是如此

public class LazyInnerClassSingleton {

	private LazyInnerClassSingleton(){
		// 这个是为了防止 通过反射机制从而破坏了单例模式
		if (LazyHolder.LAZY != null) {
			throw new RuntimeException("不允许创建多个单例!");
		}
	}
	
	public static final LazyInnerClassSingleton getInstance() {
		return LazyHolder.LAZY;
	}

	private static class LazyHolder {
		private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
	}
}

这时候在想通过反射来获取单例就会报错如下:

java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at org.about.designpatterns.singleton.test.LazyInnerClassSingletonTest.main(LazyInnerClassSingletonTest.java:26)
Caused by: java.lang.RuntimeException: 不允许创建多个单例!
	at org.about.designpatterns.singleton.lazy.LazyInnerClassSingleton.<init>(LazyInnerClassSingleton.java:18)
	... 5 more

Process finished with exit code 0

反射至此就破坏不了咱们的单例了

二、反序列化破坏单例以及解决方案

反序列化破话单例

知识补充:序列化和反序列化

序列化:把内存中的对象通过转换成字节码的形式,从而转换一个 IO 流,写入到其他地方(可以是磁盘、网络 IO),从而永久保存下来。
反序列化: 将已经持久化的字节码内容,转换为 IO 流,通过 IO 流的读取,进而将读取的内容转换为 Java 对象。在转换过程中会重新创建对象 new

当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时 再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当 于破坏了单例 。以饿汉式单例为例

继承序列化接口的单例
public class SerializableSingleton implements Serializable {

	private static final SerializableSingleton SINGLETON ;

	static {
		SINGLETON = new SerializableSingleton();
	}

	private SerializableSingleton() {
		// 防止反射破坏单例
		if (SINGLETON != null) {
			throw new RuntimeException("不允许创建多个实例!");
		}
	}

	public static SerializableSingleton getInstance() {
		return SINGLETON;
	}
}
反序列化破坏单例
public static void main(String[] args) {
		SerializableSingleton s1 = null;
		// 获取单例
		SerializableSingleton s2 = SerializableSingleton.getInstance();
		try {
			// 将s2序列化到serializable.obj中
			FileOutputStream fos = new FileOutputStream("serializable.obj");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(s2);
			oos.flush();
			oos.close();

			// 将 serializable.obj 反序列化为 s1对象
			FileInputStream fis = new FileInputStream("serializable.obj");
			ObjectInputStream ois = new ObjectInputStream(fis);
			s1 = (SerializableSingleton) ois.readObject();
			ois.close();
			
			System.out.println(s1);
			System.out.println(s2);
			// 输出为false 说明两个对象不一样,即单例被破坏了
			System.out.println(s1 == s2);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

结果如下:

org.about.designpatterns.singleton.test.SerializableSingleton@1f17ae12
org.about.designpatterns.singleton.test.SerializableSingleton@23fc625e
false

Process finished with exit code 0

解决方案

只需要增加 readResolve()方法即可。其余单例也是如此,来看优化代码:

public class SerializableSingleton implements Serializable{

	private static final SerializableSingleton SINGLETON ;

	static {
		SINGLETON = new SerializableSingleton();
	}
	
	private SerializableSingleton() {
		// 防止反射破坏单例
		if (SINGLETON != null) {
			throw new RuntimeException("不允许创建多个实例!");
		}
	}

	public static SerializableSingleton getInstance() {
		return SINGLETON;
	}

	/**
	 * 解决方案。防止序列化破坏单例
	 * readResolve 方法只不过覆盖了反序列化创建的对象。
	 * 其实还是创建了两个对象。发生在JVM层面的相对来说是比较安全的。
	 * 之前反序列化创建创建出来的对象会被 GC 回收
	 * @return SINGLETON
	 */
	private Object readResolve() {
		return SINGLETON;
	}
}

这时候运行结果如下

org.about.designpatterns.singleton.test.SerializableSingleton@23fc625e
org.about.designpatterns.singleton.test.SerializableSingleton@23fc625e
true

Process finished with exit code 0

注意一: readResolve 方法只不过覆盖了反序列化创建的对象。其实还是创建了两个对象。发生在JVM层面的相对来说是比较安全的。之前反序列化创建创建出来的对象会被 GC 回收。

注意二:枚举式单例,在JVM底层,单独对枚举做了判断处理和优化,反射和反序列化是不会破坏单例的,这也是很多书籍推荐枚举式单例的原因。

本文地址:https://blog.csdn.net/weixin_44900997/article/details/107660480

相关标签: 设计模式 java