1.反序列化
关于文件,序列化就是将对象数据存储到文件,反序列化就是将文件中存储的对象数据读取出来
下面我将以单例模式-懒汉式-“双重检查锁+volatile“来演示反序列化对单例的破坏
2.实现代码
import java.io.Serializable;
public class Singleton implements Serializable {
private volatile static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if(INSTANCE == null) {
synchronized (Singleton.class) {
if(INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
上面是单例模式-懒汉式-“双重检查锁+volatile“的实现代码,没有学过单例模式-懒汉式-“双重检查锁+volatile“的朋友,建议先看完5.Java设计模式-创建型模式-单例模式-懒汉式-“双重检查锁+volatile“实现,这样更利于大家对下面讲解的理解。
注意,我在原来代码的基础上让Singleton
实现了Serializable
接口,目的就是让Singleton
可以进行序列化,如果不实现这个接口,序列化的时候是会报错的。
public class TestDemo {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println("s1 == s2: " + (s1 == s2)); // s1 == s2: true
}
}
上面是我们测试的代码,通过Singleton.getInstance()
获取到的是同一个对象,没有问题,符合单例的特性。
下面我们要开始搞破坏了呦~
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class TestDemo {
public static void main(String[] args) throws Exception{
// 序列化对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.obj"));
oos.writeObject(Singleton.getInstance()); // 将对象写入singleton.obj文件中
// 序列化对象输入流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.obj"));
Singleton singleton = (Singleton) ois.readObject(); // 从singleton.obj文件中读取对象
System.out.println(Singleton.getInstance() == singleton); // false
}
}
上面是破坏单例的实现代码,建议大家先把注释的内容看完,再来看下面的讲解。
根据打印结果为false
,我们发现,Singleton.getInstance()
和singleton
不是同一个对象,即单例遭到了破坏,即Singleton
的实例不是全局唯一的。
那我们该怎么解决呢?
其实解决这个问题很好办,我们往下看。
在实现Serializable
接口的基础上了,我又加了一个如上图所示的readResolve
方法。
加了这个方法后,我们再来测试,大家也可以测试一下,会发出打印的结果为true
了。
为什么加了这个方法就能解决反序列化对单例的破坏呢?
我们来看一下这个readResolve
方法的作用:防止反序列化破坏单例,由于程序在反序列化时,也就是执行ois.readObject
方法时(这个方法在我们搞破坏时执行过),在源码内会判断是否有readResolve
方法,如果有,则会调用该方法获取实例,如果没有,则会获取反射创建的实例。
3.总结
大家可以就单例模式-饿汉式、单例模式-懒汉式-“synchronized加锁“实现、单例模式-静态内部类,自己来实现一下如何避免反序列化对单例的破坏,其实不难,只要实现readResolve
方法并且在这个方法内编写返回单例对象的代码即可。
最后的问题:为什么用枚举实现单例直接就能避免反序列化对单例的破坏呢?
答案:枚举常量在序列化时只序列化枚举常量的名称(不是序列化对象的数据),反序列化时根据枚举常量的名称来获取内存中对应的实例对象,所以反序列化无法破坏用枚举实现的单例