这个问题的意思就是在我们使用单例模式时首先获取一个对象instance,然后经过了序列化保存到磁盘中(或者网络二进制流),再将磁盘上的二进制文件反序列化为Java程序中的一个对象,问这个对象和原来的instance对象是不是同一个对象。我们用代码来复现一下这个问题:
package com.example.demo;
import java.io.*;
public class SingletonSerializable implements Serializable {
private static SingletonSerializable instance;
public static synchronized SingletonSerializable getInstance() {
if (instance == null) {
instance = new SingletonSerializable();
}
return instance;
}
public static void main(String[] args) throws Exception {
SingletonSerializable instance1 = SingletonSerializable.getInstance();
/**
* 通过反序列化的方式构造多个对象(类需要实现Serializable接口)
*/
// 1. 把对象sc1写入硬盘文件
FileOutputStream fos = new FileOutputStream("./object.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
System.out.println(instance1);
oos.close();
fos.close();
// 2. 把硬盘文件上的对象读出来
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./object.txt"));
// 如果对象定义了readResolve()方法,readObject()会调用readResolve()方法。从而解决反序列化的漏洞
SingletonSerializable instance2 = (SingletonSerializable) ois.readObject();
// 反序列化出来的对象,和原对象,不是同一个对象。如果对象定义了readResolve()方法,可以解决此问题。
System.out.println(instance2);
System.out.println(instance1 == instance2);
ois.close();
}
}
输出:
com.example.demo.SingletonSerializable@79fc0f2f
com.example.demo.SingletonSerializable@4bf558aa
false
很显然,结果是两个对象不一样。
那么如何解决这个问题呢?很简单,只要在你的单例类中添加一个readResolve()
方法,返回单例对象即可:
// 防止反序列化获取多个对象的漏洞。
// 无论是实现Serializable接口,或是Externalizable接口,当从I/O流中读取对象时,readResolve()方法都会被调用到。
// 实际上就是用readResolve()中返回的对象直接替换在反序列化过程中创建的对象。
private Object readResolve() throws ObjectStreamException {
return instance;
}
再重新运行一下,输出:
com.example.demo.SingletonSerializable@79fc0f2f
com.example.demo.SingletonSerializable@79fc0f2f
true
THE END.