在前一篇文章中,对单例模式列举了五种实现方式。其中枚举模式拥有出生光环,天生就没有反射及反序列化漏洞。针对其他四种实现方式,在本篇文章中对懒汉式单例模式实现进行反射及反序列化漏洞测试。
一、通过反射破解懒汉式单例模式
重新创建测试类TestClientNew,通过反射获取到类,使用newInstance进行初始化。代码如下(详细看注释):
package com.zwh.gof23.singleton;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 单例模式测试类,使用反射几反序列化破解懒汉式单例模式
*
* @author zwh
*
*/
public class TestClientNew {
/**
* @param args
* @throws ClassNotFoundException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalArgumentException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void main(String[] args) throws ClassNotFoundException,
NoSuchMethodException, SecurityException, InstantiationException,
IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
testSingletonPatternReflect();
}
/**
* 通过反射破解单例模式(懒汉式为例)
*
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
private static void testSingletonPatternReflect()
throws ClassNotFoundException, NoSuchMethodException,
SecurityException, InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
SingletonPatternSlackerNew s = SingletonPatternSlackerNew.getInstance();
System.out.println(s);
// 通过反射得到懒汉式单例模式实现类
Class<SingletonPatternSlackerNew> clazz = (Class<SingletonPatternSlackerNew>) Class
.forName("com.zwh.gof23.singleton.SingletonPatternSlackerNew");
// 获取无参构造器
Constructor<SingletonPatternSlackerNew> c = clazz
.getDeclaredConstructor(null);
// 设置私有无参构造器可以访问,跳过权限检查
c.setAccessible(true);
// 初始化类的实例
SingletonPatternSlackerNew s1 = c.newInstance();
SingletonPatternSlackerNew s2 = c.newInstance();
System.out.println(s1);
System.out.println(s2);
}
}
运行程序,输出如下:
com.zwh.gof23.singleton.SingletonPatternSlackerNew@6d06d69c
com.zwh.gof23.singleton.SingletonPatternSlackerNew@7852e922
com.zwh.gof23.singleton.SingletonPatternSlackerNew@4e25154f
可得知,本此时方法得到了三个不同的实例。单例模式至此被破解。
二、预防反射破解单例模式
既然有破解的方式,那就可以防止被破解。通过在私有的构造器内进行实例判断并抛出异常,可以防止单例模式被利用反射破解。
私有构造器改造如下:
//私有化构造器
private SingletonPatternSlackerNew(){
//多次调用时,抛出异常
if(instance!=null){
throw new RuntimeException("已存在实例,别想用反射来搞我!");
}
}
改造后,再次运行测试方法。结果如下:
com.zwh.gof23.singleton.SingletonPatternSlackerNew@6d06d69c
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.zwh.gof23.singleton.TestClientNew.testSingletonPatternReflect(TestClientNew.java:58)
at com.zwh.gof23.singleton.TestClientNew.main(TestClientNew.java:28)
Caused by: java.lang.RuntimeException: 已存在实例,别想用反射来搞我!
at com.zwh.gof23.singleton.SingletonPatternSlackerNew.<init>(SingletonPatternSlackerNew.java:17)
... 6 more
此刻发现这样的破解方式被有效防止了,但如果第一个实例,也是通过反射来创建的,这种方式就无法生效了。测试代码如下:
/**
* 通过反射破解单例模式(懒汉式为例)
*
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws SecurityException
* @throws InstantiationException
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
private static void testSingletonPatternReflect()
throws ClassNotFoundException, NoSuchMethodException,
SecurityException, InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
/*SingletonPatternSlackerNew s = SingletonPatternSlackerNew.getInstance();
System.out.println(s);*/
// 通过反射得到懒汉式单例模式实现类
Class<SingletonPatternSlackerNew> clazz = (Class<SingletonPatternSlackerNew>) Class
.forName("com.zwh.gof23.singleton.SingletonPatternSlackerNew");
// 获取无参构造器
Constructor<SingletonPatternSlackerNew> c = clazz
.getDeclaredConstructor(null);
// 设置私有无参构造器可以访问,跳过权限检查
c.setAccessible(true);
// 初始化类的实例
SingletonPatternSlackerNew s1 = c.newInstance();
SingletonPatternSlackerNew s2 = c.newInstance();
System.out.println(s1);
System.out.println(s2);
}
在这次测试中将直接调用getInstance方法得到实例的代码注释掉,两个实例都通过反射来创建。输出结果:
com.zwh.gof23.singleton.SingletonPatternSlackerNew@6d06d69c
com.zwh.gof23.singleton.SingletonPatternSlackerNew@7852e922
这说明构造器中的方法还无法避免被反射破解。对构造器进行修改。最终代码如下:
package com.zwh.gof23.singleton;
import java.io.Serializable;
/**
* 懒汉式到单例模式(防序列化及反射漏洞)
* @author zwh
* 特点:
* 1、延迟加载,在调用到getInstance方法时才加载。资源利用率高
* 2、调用getInstance方法时需要同步,并发效率较低
*/
public class SingletonPatternSlackerNew implements Serializable{
//实例不初始化,需要时再进行初始化
private static SingletonPatternSlackerNew instance;
private static int count = 0;
//私有化构造器
private SingletonPatternSlackerNew(){
synchronized (SingletonPatternSlackerNew.class) {
if(count > 0){
throw new RuntimeException("创建了两个实例");
}
count++;
}
//多次调用时,抛出异常
if(instance!=null){
throw new RuntimeException("已存在实例,别想用反射来搞我!");
}
}
/**
* 初始化类的实例,并保证在调用本方法时才会创建实例,保证线程安全。
* @return
*/
public static synchronized SingletonPatternSlackerNew getInstance(){
if(null == instance){
instance = new SingletonPatternSlackerNew();
}
return instance;
}
/**
* 防止被反序列化破解单例
* @return
*/
private Object readResolve(){
return instance;
}
}
再次运行测试代码,结果如下:
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.zwh.gof23.singleton.TestClientNew.testSingletonPatternReflect(TestClientNew.java:90)
at com.zwh.gof23.singleton.TestClientNew.main(TestClientNew.java:36)
Caused by: java.lang.RuntimeException: 创建了两个实例
at com.zwh.gof23.singleton.SingletonPatternSlackerNew.<init>(SingletonPatternSlackerNew.java:21)
... 6 more
避免了单例被反射多次创建。
注:构造器内的判断instance为空代码可以去掉。
到此,防止反射破解单例模式完成。
三、通过反序列化破解懒汉式单例模式
除了前面描述的反射来破解单例模式,通过反序列化,也可以破解单例模式。当然前提是,实现单例模式的类需要可以序列化。通过实现Serializable接口即可。
测试方法如下:
/**
* 通过反序列化破解单例模式漏洞,前提是单例类可序列化。
* @throws IOException
* @throws ClassNotFoundException
*/
private static void testSingletonPatternSerialize() throws IOException, ClassNotFoundException{
SingletonPatternSlackerNew s=SingletonPatternSlackerNew.getInstance();
System.out.println(s);
FileOutputStream fos=new FileOutputStream("d:/a.txt");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(s);
oos.close();
fos.close();
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("d:/a.txt"));
SingletonPatternSlackerNew s1=(SingletonPatternSlackerNew) ois.readObject();
System.out.println(s1);
}
运行结果:
com.zwh.gof23.singleton.SingletonPatternSlackerNew@6d06d69c
com.zwh.gof23.singleton.SingletonPatternSlackerNew@eed1f14
可见,产生了两个不同的实例。
四、防止反序列化破解单例
要防止被反序列化破解单例。一种不准单例类被序列化。另一种是在单例类中声明readResolve方法,返回实例。代码如下:
/**
* 防止被反序列化破解单例
* @return
*/
private Object readResolve(){
return instance;
}
再次运行输出结果:
com.zwh.gof23.singleton.SingletonPatternSlackerNew@6d06d69c
com.zwh.gof23.singleton.SingletonPatternSlackerNew@6d06d69c
两次得到了同一个实例。
至此,通过反射几反序列化破解单例模式,以及如何防止单例模式被反射和反序列化的破解学习完毕。