反射破坏单例模式以及怎样防御( 三 )


静态内部类式
private Singleton(){if(InnerClass.singleton!=null){throw new RuntimeException("单例模式下 , 禁止使用反射创建新实例");}}
这两种方式通过更改构造器的代码都可以有效防止反射创建新实例
我们接下来对用双重检查锁式实现的单例模式进行测试 。为了展示懒汉式使用(改动构造函数来防止反射攻击)方法的漏洞 。我将类引用变量设置成了,方便测试代码获取打印到控制台
public class Singleton {public static volatile Singleton singleton;private Singleton() {if(singleton!=null){throw new RuntimeException("单例模式下 , 禁止使用反射创建新实例");}}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class){if(singleton==null){singleton = new Singleton();}}}return singleton;}}
测试代码如下 , 有详细的注释
public class ReflectTest {public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException {//得到该类在内存中的字节码对象Class objectClass = Class.forName("com.xt.designmode.creational.singleton.doubleCheckClass.Singleton");//获取这个对象的构造器Constructor constructor = objectClass.getDeclaredConstructor();//暴力反射 , 解除私有限定constructor.setAccessible(true);//先使用反射获取的构造器创建一个实例Singleton reflectInstance = (Singleton) constructor.newInstance();//打印反射机制创造的实例System.out.println(reflectInstance);//打印此时单例类中的Singleton类引用变量singletonSystem.out.println(Singleton.singleton);//使用常规方式获取实例Singleton instance = Singleton.getInstance();//打印常规方式生成的实例System.out.println(instance);//对象类型 ,  == 比较的是地址if(instance != reflectInstance){System.out.printf("创建了两个实例\n");}else{System.out.printf("只创建了一个实例\n");}}}
如下图所示 , 创建了两个实例 , 是因为正常的双重检查锁式只是想实例化单例类中的一个引用变量 , 如果在单例类还没有实例化这个引用变量之前 , 反射就可以通过使用构造器创建无数个实例 , 直到有人使用常规的方式来将单例类中的引用变量实例化 。
本来懒汉式只是为了节约资源 , 等到真正有人使用他的时候才创建 , 但是没想到给反射钻了空子 。
既然他钻这个空子 , 我们就想别的办法 , 因为我们只需要一个实例 , 而懒汉式的实例都是通过构造函数来创建的 , 那我们只需要保证构造函数只会被调用一次就好了 。我们加个标记flag
public class Singleton {private static volatile Singleton singleton;private static boolean flag = true;private Singleton() {if(flag == true){flag = false;}else{throw new RuntimeException("单例模式下 , 禁止使用反射创建新实例");}}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class){if(singleton==null){singleton = new Singleton();}}}return singleton;}}
这样就限制了构造函数的使用次数 , 可以有效防止反射通过获取构造器来创建新实例 。
但是反射是可以获取指定类的一切属性和方法 , 所以也可以通过反射不断将flag置为true来破解这一限制
public class ReflectTest {public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, NoSuchFieldException {//得到该类在内存中的字节码对象Class objectClass = Class.forName("com.xt.designmode.creational.singleton.doubleCheckClass.Singleton");//获取这个对象的构造器Constructor constructor = objectClass.getDeclaredConstructor();//暴力反射 , 解除私有限定constructor.setAccessible(true);for(int i=0;i