反射和反序列化破坏单例及解决方案
反射和反序列化破坏单例及解决方案
一、反射破坏单例以及解决方案
反射破坏单例
上面介绍的单例模式的构造方法除了私有化加上 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
上一篇: linux下jdk的安装及环境变量配置
下一篇: 线程的创建与基本方法的使用