vlambda博客
学习文章列表

如何避免反射和序列化破坏单例模式

单例模式的研究重点有以下几个:

  1. 构造私有,提供静态输出接口

  2. 线程安全,确保全局唯一

  3. 延迟初始化

  4. 防止反射攻击

  5. 防止序列化破坏单例模式

上一节《单例设计模式实现总结》,我们使用饿汉式、双重锁检查、静态内部类、枚举类实践了前3条。然而光并发安全并不能保证唯一实例,反射和序列化可以破坏单例模式。

public classReflectSingleton{
private final static ReflectSingleton instance = new ReflectSingleton();

privateReflectSingleton() {
}

publicstatic ReflectSingleton getInstance() {
return instance;
}
}

本文中采用饿汉单例模式作为最初代码,演示如何避免反射和序列化破坏单例模式。

防止反射攻击

使用反射攻击单例模式

public classClient{
public static void main(String[] args) throws Exception {
// 通过全局访问方法创建实例
ReflectSingleton instance = ReflectSingleton.getInstance();
// 通过反射创建实例
Constructor constructor = ReflectSingleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
ReflectSingleton newInstance = (ReflectSingleton) constructor.newInstance();

System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}

使用反射时,需要添加构造器权限,否则会抛异常。

Exception in thread "main" java.lang.IllegalAccessException: Class com.lzp.java.concurrent.singleton.destroysingleton.Client can not access a member of class com.lzp.java.concurrent.singleton.destroysingleton.ReflectSingleton with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Constructor.newInstance(Constructor.java:413)
at com.lzp.java.concurrent.singleton.destroysingleton.Client.main(Client.java:16)

运行结果:

com.lzp.java.concurrent.singleton.destroysingleton.ReflectSingleton@355da254
com.lzp.java.concurrent.singleton.destroysingleton.ReflectSingleton@4dc63996
false

改进措施:反射防御

抵御这种攻击,可以在构造器中添加反射防御代码,让它在被要求创建第二个实例时抛出异常。

privateReflectSingleton() {
if (instance != null) {
throw new RuntimeException("禁止反射调用创建多个实例");
}
}

运行结果:

Exception in thread "main" 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 com.lzp.java.concurrent.singleton.destroysingleton.Client.main(Client.java:18)
Caused by: java.lang.RuntimeException: 禁止反射调用创建多个实例

需要注意的是,在构造器中添加反射防御代码,仅适用于基于类初始化加载的单例实现,即饿汉式和静态内部类实现。对于双重锁检查不会出现反射攻击的情况。

防止序列化破坏单例模式

反序列化问题

public class Client2 {
publicstaticvoidmain(String[] args) throws Exception {
// 使用全局访问方法创建实例
SerializeSingleton instance = SerializeSingleton.getInstance();

// 写出对象到项目目录下singleton.txt文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.txt"));
oos.writeObject(instance);
// 读入对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.txt"));
SerializeSingleton newInstance = (SerializeSingleton) ois.readObject();

System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}

运行结果:

com.lzp.java.concurrent.singleton.destroysingleton.SerializeSingleton@4b1210ee
com.lzp.java.concurrent.singleton.destroysingleton.SerializeSingleton@27973e9b
false

改进措施:添加readResolve()方法

private Object readResolve() {
return instance;
}

运行结果:

com.lzp.java.concurrent.singleton.destroysingleton.SerializeSingleton@4b1210ee
com.lzp.java.concurrent.singleton.destroysingleton.SerializeSingleton@4b1210ee
true

为什么是readResolve(),而不是其他方法?

此时可以对源码做单步调试。

// 核心语句
SerializeSingleton newInstance = (SerializeSingleton) ois.readObject();

// ObjectInputStream
publicfinal Object readObject(){
...
try {
Object obj = readObject0(false);
.....
}
}

readObject方法内部调用readObject0方法。

// ObjectInputStream
private Object readObject0(boolean unshared) throws IOException {
......
try {
switch (tc) {
........
case TC_OBJECT: // 如果是读取对象Object
return checkResolve(readOrdinaryObject(unshared));
.........
default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}

定位到关键方法readOrdinaryObject()。

// ObjectInputStream
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
.......
Object obj;
try {
// 注:如果为true,通过反射创建新的实例
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())
{
// 内部核心语句:return readResolveMethod.invoke(obj, (Object[]) null);
// 反射创建原实例
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
// 替换对象
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;
}

/**
* 如果类是可序列化的,返回true
*/

booleanisInstantiable() {
requireInitialized();
return (cons != null);
}

/**
* 如果类是可序列化的,并且定义了readResolve()方法,返回true;否则返回false
*/

booleanhasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}

通过调试,我们可以看出,调readObject()方法反序列化的过程中,总会创建一个新的实例。如果SerializeSingleton类中定义了readResolve方法,就通过反射创建原实例,返回时覆盖之前创建的实例。否则,返回新的实例。

此时,我们便清楚了为什么用的是readResolve方法,而不是其他方法。