单例模式的研究重点有以下几个:
- 构造私有,提供静态输出接口
- 线程安全,确保全局唯一
- 延迟初始化
- 防止反射攻击
- 防止序列化破坏单例模式
上一节《单例设计模式实现总结》,我们使用饿汉式、双重锁检查、静态内部类、枚举类实践了前3条。然而光并发安全并不能保证唯一实例,反射和序列化可以破坏单例模式。
public class ReflectSingleton {
private final static ReflectSingleton instance = new ReflectSingleton();
private ReflectSingleton() {
}
public static ReflectSingleton getInstance() {
return instance;
}
}
本文中采用饿汉单例模式作为最初代码,演示如何避免反射和序列化破坏单例模式。
防止反射攻击
使用反射攻击单例模式
public class Client {
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
改进措施:反射防御
抵御这种攻击,可以在构造器中添加反射防御代码,让它在被要求创建第二个实例时抛出异常。
private ReflectSingleton() {
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 {
public static void main(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
public final 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
*/
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
/**
* 如果类是可序列化的,并且定义了readResolve()方法,返回true;否则返回false
*/
boolean hasReadResolveMethod() {
requireInitialized();
return (readResolveMethod != null);
}
通过调试,我们可以看出,调readObject()方法反序列化的过程中,总会创建一个新的实例。如果SerializeSingleton类中定义了readResolve方法,就通过反射创建原实例,返回时覆盖之前创建的实例。否则,返回新的实例。
通过底层代码分析,我们便清楚了为什么用的是readResolve方法,而不是其他。