序列化破坏单例模式原理以及解决方案
/**
* 单例模式:饿汉式,线程安全,在类初始化的过程中就已经被创建赋值
*/
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
/**
* 运行结果
* com.nrael.design.pattern.creational.singleton.HungrySingleton@135fbaa4
* com.nrael.design.pattern.creational.singleton.HungrySingleton@568db2f2
* false
*/
结果发现,经过序列化和反序列化之后拿到的对象是两个,这违背了单例模式的初衷。
通过跟踪源码,发现可以通过在单例类中,增加以下方法,可以解决上述问题。注意该方法名称和返回类型最好跟下面所写一致。
/**
* 单例模式:饿汉式,线程安全,在类初始化的过程中就已经被创建赋值
*/
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
// 该方法并非被重写的方法
private Object readResolve(){
return hungrySingleton;
}
}
再次调用 Test 运行结果
com.nrael.design.pattern.creational.singleton.HungrySingleton@135fbaa4
com.nrael.design.pattern.creational.singleton.HungrySingleton@135fbaa4
true
源码跟踪:
// ObjectInputStream#readObject
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
}
// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
// TODO 从这里查看
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
// ObjectInputStream#readObject0
private Object readObject0(boolean unshared) throws IOException {
...
case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared));
...
}
// ObjectInputStream#readOrdinaryObject
private Object readOrdinaryObject(boolean unshared){
...
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
}
// ObjectInputStream#isInstantiable 返回 true,执行 desc.newInstance(),通过反射创建新的单例类,
// 到此时也看到了为什么在 HungrySingleton 没添加 readResolve 方法之前会返回新的对象.
/**
* Returns true if represented class is serializable/externalizable and can
* be instantiated by the serialization runtime--i.e., if it is
* externalizable and defines a public no-arg constructor, or if it is
* non-externalizable and its first non-serializable superclass defines an
* accessible no-arg constructor. Otherwise, returns false.
*/
boolean isInstantiable() {
requireInitialized();
return (cons != null);
}
// ObjectInputStream#readOrdinaryObject
private Object readOrdinaryObject(boolean unshared){
...
// 在 HungrySingleton 添加 readResolve 方法之后 desc.hasReadResolveMethod() 该方法执行为 true
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
// 通过反射调用 HungrySingleton 类中的 readResolve 方法返回,
// 即为我们的单例对象,所以这里讲此处返回的对象赋值给 obj,所以这里我们找到了答案
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;
}
//
readResolveMethod = getInheritableMethod(
// 这里有 readResolve 解答了我们 HungrySingleton 类中方法命名疑惑
cl, "readResolve", null, Object.class);
反射攻击解决方案及原理分析
在类加载的时候就已经生成单例实例对象的单例模式:
/**
* 单例模式:饿汉式,线程安全,在类初始化的过程中就已经被创建赋值
*/
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
/**
* 反射攻击解决方案及原理
* 破坏原理:通过反射暴力访问
* 解决原理:在单例类的私有化构造方法中添加防放射破坏代码,如果是通过反射调用就抛出运行时异常
*/
public class Test {
public static void main(String[] args) throws Exception {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
HungrySingleton instance = HungrySingleton.getInstance();
constructor.setAccessible(true);
HungrySingleton object = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(object);
System.out.println(instance == object);
/**
* 运行结果
* com.nrael.design.pattern.creational.singleton.v2.HungrySingleton@1540e19d
* com.nrael.design.pattern.creational.singleton.v2.HungrySingleton@677327b6
* false
*/
}
}
解决方案:在私有化构造器中添加防止通过反射调用的代码(只针对在类加载初始化时就已经创建好单例对象的单例模式有效)即(饿汉式和基于内部类实现懒加载的单例模式有效,对其他懒加载无效)
/**
* 单例模式:饿汉式,线程安全,在类初始化的过程中就已经被创建赋值
*/
public class HungrySingleton implements Serializable {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
// 防放射破坏代码,如果是通过反射调用就抛出运行时异常
if (hungrySingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
========================================
/**
* 单例:静态内部类,基于类初始化的延迟加载解决方案
*/
public class StaticInnerClassSingleton {
// 私有化构造器
private StaticInnerClassSingleton() {
// 在单例类的私有化构造方法中添加防放射破坏代码,如果是通过反射调用就抛出运行时异常
if (InnerClass.staticInnerClassSingleton != null) {
throw new RuntimeException("单例构造器禁止反射调用");
}
}
// 静态内部类的静态初始化锁,哪个线程拿到哪个线程就去初始化它
private static class InnerClass {
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance() {
return InnerClass.staticInnerClassSingleton;
}
}
再次调用 Test 类,控制台输出结果如下
/**
* 运行结果
* 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.nrael.design.pattern.creational.singleton.v2.Test.main(Test.java:16)
* Caused by: java.lang.RuntimeException: 单例构造器禁止反射调用
* at com.nrael.design.pattern.creational.singleton.v2.HungrySingleton.(HungrySingleton.java:14)
* ... 5 more
*/
对于不是在类加载的时候创建好单例实例对象的单例模式如何避免单例被破坏呢?
验证结果是无法避免,因为只要你不是在类加载时就创建好单例对象,通过反射的方式总能破坏一切。感叹反射技术的强大。这里不演示了。
克隆破坏单例
/**
* 单例模式:饿汉式,线程安全,在类初始化的过程中就已经被创建赋值
* 使用克隆破坏单例
*/
public class HungrySingleton implements Serializable, Cloneable {
private final static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
// 该方法并非被重写的方法
private Object readResolve(){
return hungrySingleton;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// return super.clone();
// 解决办法,1. 不实现 Cloneable 接口,2. 实现 Cloneable 接口,重写 clone 方法,调用 单例类的 getInstance 方法
return getInstance();
}
}
/**
* 通过原型模式的克隆方法破坏单例
*/
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
method.setAccessible(true);
HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton);
System.out.println(hungrySingleton);
System.out.println(cloneHungrySingleton);
System.out.println(hungrySingleton == cloneHungrySingleton);
/**
* 运行结果
* com.nrael.design.pattern.creational.singleton.v4.HungrySingleton@7f31245a
* com.nrael.design.pattern.creational.singleton.v4.HungrySingleton@6d6f6e28
* false
*/
/**
* 添加相关代码,解决克隆破坏单例,再次运行
* 运行结果
* com.nrael.design.pattern.creational.singleton.v4.HungrySingleton@7f31245a
* com.nrael.design.pattern.creational.singleton.v4.HungrySingleton@7f31245a
* true
*/
}
}