先看代码:
csharp
public class Singleton {
private Singleton(){}
private static class SingletonInstance{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.instance;
}
}
我想应该没有不知道这行个类是干嘛的小伙伴了吧,这是单例模式的一种写法。
单例模式是每一个 Java boy 必须要掌握的设计模式,它所描述的是在某个进程内,某个类有且仅有一个实例。我们知道要破坏单例模式就必须让它创建多个对象。创建对象的方式无非就几种:
- new
- clone
- 反射
- 反序列化
首先单例模式的构造器一定是 private 的,所以 new 这种方式是无法破坏单例模式的。 而 clone 需要实现 Cloneable 接口,单例模式谁如果实现了这个接口,请打死它。所以就剩下反射和反序列化了。本篇文章只讨论反序列化。
反序列化破坏单例模式
与 clone 方式一样,反序列化需要实现 Serializable 接口,但是有小伙伴可能会说,谁会在单例模式中实现 Serializable 接口咯,除非他疯了,确实是这种情况,但是在实际情况中它并不是一定会避免的,有些类它就是一定要序列化。比如单例对象在不同环境或应用实例之间的共享、持久化或状态恢复,当然这些场景都属于比较特殊的场景。
继续用上面例子:
java
public class SerializableSingleton implements Serializable {
// 省略部分代码
}
然后在对该类进行序列化和反序列化
java
public class Test {
public static void main(String[] args) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.txt"));
SerializableSingleton singleton = SerializableSingleton.getInstance();
oos.writeObject(singleton);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Singleton.txt"));
SerializableSingleton singleton1 = (SerializableSingleton) ois.readObject();
System.out.println("singleton = singleton1:" + (singleton == singleton1));
}
}
运行结果
ini
singleton = singleton1:false
通过对 Singleton 进行反序列化得到了一个全新的对象,这就破坏了 Singleton 的单例性了。我们看 readObject()
源码就知道了。
java
public final Object readObject() throws IOException, ClassNotFoundException {
//...
int outerHandle = passHandle;
try {
Object obj = readObject0(false);
//...
return obj;
} finally {
//...
}
}
调用 readObject0()
:
java
private Object readObject0(boolean unshared) throws IOException {
// ...
try {
switch (tc) {
// ...
case TC_OBJECT:
// readObject0()
return checkResolve(readOrdinaryObject(unshared));
// ...
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
readObject0()
是根据反序列化对象的不同执行不同的方法来反序列化一个实例对象。我们这里是 Object,所以进一步看 readOrdinaryObject()
。
scss
private Object readOrdinaryObject(boolean unshared) throws IOException {
// ...
Object obj;
try {
// 核心代码
// 反射创建一个新对象
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);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// Filter the replacement object
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
这段代码最核心的地方就是:
ini
obj = desc.isInstantiable() ? desc.newInstance() : null;
底层依然是利用反射的方式来创建一个新对象。
那么对于这种方式有什么保护措施没?在 readOrdinaryObject()
最后面一段就已经告知了:
csharp
if (obj != null && handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod()) {
//....
}
判断反序列化的类是否已实现了 readResolve()
,如果有则会调用该方法,我们只需要在该方法里面返回原对象就可以了。验证下。
typescript
public class SerializableSingleton implements Serializable {
// ...
private Object readResolve() {
return SingletonInstance.instance;
}
}
执行结果:
ini
singleton = singleton1:true
执行结果为 true,就说明序列化和反序列化出来的是同一个对象。
所以,要想防止单例被反序列化破坏,就让单例实现 readResolve()
方法,返回同一个对象即可。