之前写过几篇单例的文章:
Java单例—双重锁校验详解
Java单例—序列化破坏单例模式原理解析
Java单例—静态内部类
在静态内部类中引出了反射攻击的问题,本篇就来说一下反射攻击,废话不多少说上代码:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test1 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objClass = StaticInnerClass.class;
//获取类的构造器
Constructor constructor = objClass.getDeclaredConstructor();
//把构造器私有权限放开
constructor.setAccessible(true);
//正常的获取实例方式
StaticInnerClass staticInnerClass = StaticInnerClass.getInstance();
//反射创建实例
StaticInnerClass newStaticInnerClass = (StaticInnerClass) constructor.newInstance();
System.out.println(staticInnerClass);
System.out.println(newStaticInnerClass);
System.out.println(staticInnerClass == newStaticInnerClass);
}
}
上面这个代码的运行结果:
com.ygz.designpatterns.singleton.StaticInnerClass@4d7e1886
com.ygz.designpatterns.singleton.StaticInnerClass@3cd1a2f1
false
出现了两个不同的实例,这就违反了我们使用单例原则,不能保证只有一个实例,那么如何解决呢?还是直接上代码:
public class StaticInnerClass {
private static class InnerClass{
private static StaticInnerClass staticInnerClass = new StaticInnerClass();
}
public static StaticInnerClass getInstance(){
return InnerClass.staticInnerClass;
}
private StaticInnerClass(){
//构造器判断,防止反射攻击,大家可以在下面这行if判断打断点来测试一下这个方法的过程,很好理解的
if(InnerClass.staticInnerClass != null){
throw new IllegalStateException();
}
}
}
这样写就可以防止反射攻击啦,这种方式同样适用于其他的饿汉模式防御反射攻击。但是如果不是在类加载的时候创建对象实例的这种单例,是没有办法防止反射攻击的,比如之前写过的那个双重锁校验,使用这种在构造器判断方式是无效的,来段代码证明一下,先上双重锁校验的构造器判断方式代码:
import java.io.Serializable;
/**
* 双重锁校验的单例
*/
public class DoubleLock implements Serializable {
public static volatile DoubleLock doubleLock = null;//volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取)
private DoubleLock(){
//构造器必须私有 不然直接new就可以创建
//构造器判断,防止反射攻击
if(doubleLock != null){
throw new IllegalStateException();
}
}
public static DoubleLock getInstance(){
if(doubleLock == null){
synchronized (DoubleLock.class){
if(doubleLock == null){
doubleLock = new DoubleLock();
}
}
}
return doubleLock;
}
private Object readResolve(){
return doubleLock;
}
}
这段代码其他注释就不写了,感兴趣的可以直接去看文章开头的几篇博客。从这段代码可以看到我们采用同样的方式防止反射攻击,接下来我们测试一下他是不是可以防止反射攻击:
public class Test1 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objClass = DoubleLock.class;
//获取类的构造器
Constructor constructor = objClass.getDeclaredConstructor();
//把构造器私有权限放开
constructor.setAccessible(true);
//反射创建实例 注意反射创建要放在前面,才会攻击成功,因为如果反射攻击在后面,先使用正常的方式创建实例的话,在构造器中判断是可以防止反射攻击、抛出异常的,
//因为先使用正常的方式已经创建了实例,会进入if
DoubleLock o1= (DoubleLock) constructor.newInstance();
//正常的获取实例方式 正常的方式放在反射创建实例后面,这样当反射创建成功后,单例对象中的引用其实还是空的,反射攻击才能成功
DoubleLock o2= DoubleLock.getInstance();
System.out.println(o2);
System.out.println(o1);
System.out.println(o1 == o2);
}
}
上面是测试代码,注意这段代码中要先进行反射创建实例,再进行正常的getInstance()
创建实例,才能攻击成功,如果先getInstance()
再反射创建,构造器中的判断是可以防止的,原因注释里面也有写,就不重复了。
还有一种尝试解决这种反射攻击的是:在单例里面加标识属性,如果实例化之后,标识改变,在构造器里面判断标识改变就抛异常,和上面这种气势差不多,但是没用的,反射可以把构造器的权限放开,同样可以把属性的权限放开,并且修改属性值,所以这种方式也是不行的,我还是写一下这个代码吧,方便大家理解,首先上加了标识属性的单例:
import java.io.Serializable;
/**
* 双重锁校验的单例
*/
public class DoubleLock implements Serializable {
public static volatile DoubleLock doubleLock = null;//volatile防止指令重排序,内存可见(缓存中的变化及时刷到主存,并且其他的内存失效,必须从主存获取)
private static boolean flag = true; //设置标识属性
private DoubleLock(){
if(flag){ //初始为true,进入构造器正常创建对象,然后把flag的值改成false,后面的就会抛异常
flag = false;
}else{
throw new IllegalStateException();
}
}
public static DoubleLock getInstance(){
if(doubleLock == null){
synchronized (DoubleLock.class){
if(doubleLock == null){
doubleLock = new DoubleLock();
}
}
}
return doubleLock;
}
private Object readResolve(){
return doubleLock;
}
}
这段代码中加了一个flag属性用来标识是否已经创建了实例,接下来我们来通过反射破坏这个单例:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class Test1 {
public static void main(String[] args) throws Exception {
Class objClass = DoubleLock.class;
Constructor constructor = objClass.getDeclaredConstructor();
//构造器权限
constructor.setAccessible(true);
//正常获取实例
DoubleLock o1 = DoubleLock.getInstance();
//获取到这个实例的flag属性
Field flag = o1.getClass().getDeclaredField("flag");
//属性权限放开
flag.setAccessible(true);
//属性值改为true
flag.set(o1, true);
DoubleLock o2 = (DoubleLock) constructor.newInstance();
System.out.println(o1);
System.out.println(o2);
System.out.println(o1 == o2);
}
}
这段代码运行结果:
com.ygz.designpatterns.singleton.DoubleLock@2f0e140b
com.ygz.designpatterns.singleton.DoubleLock@7440e464
false
可以看到还是破坏了单例。
先写到这里,后面看有补充的继续补充
个人浅薄理解,欢迎补充