在我的上篇随笔中,我们知道了创建单例类有以下几种方式:
(1).饿汉式;
(2).懒汉式(、加同步锁的懒汉式、加双重校验锁的懒汉式、防止指令重排优化的懒汉式);
(3).登记式单例模式;
(4).静态内部类单例模式;
(5).枚举类型的单例模式。
在上面的5种实现方式中,除了枚举类型外,其他的实现方式是可以被JAVA的反射机制给攻击的,即使他的构造方法是私有化的,我们也可以做一下处理,从外部得到它的实例。
下面,我将会举例来说明:
说明:
Singleton.java 没有经过处理的饿汉式单例模式实现方式
Singleton6.java 枚举类型的单例模式
SingletonNotAttackByReflect.java 经过处理的饿汉式单例模式实现方式
SingletonReflectAttack.java 具体反射类
SingletonReflectAttackMain.java JUnit测试类
举例1:不经过处理的单例类被JAVA反射机制攻击
Singleton.java 代码清单【1.1】
1 public classSingleton2 {3 private static boolean flag = true;4 private static final Singleton INSTANCE = newSingleton();5
6 privateSingleton()7 {8 }9
10 public staticSingleton newInstance()11 {12 returnINSTANCE;13 }14
15 }
SingletonReflectAttack.java 代码清单【1.2】
1 /**
2 * 单例模式被java反射攻击3 *@throwsIllegalArgumentException4 *@throwsInstantiationException5 *@throwsIllegalAccessException6 *@throwsInvocationTargetException7 *@throwsSecurityException8 *@throwsNoSuchMethodException9 */
10
11 public static void attack() throwsIllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException12 {13 Class> classType = Singleton.class;14 Constructor> constructor = classType.getDeclaredConstructor(null);15 constructor.setAccessible(true);16 Singleton singleton =(Singleton) constructor.newInstance();17 Singleton singleton2 =Singleton.newInstance();18 System.out.println(singleton == singleton2); //false
19 }
测试结果:SingletonReflectAttackMain.java 代码清单【1.3】
1 /**
2 * 1.测试单例模式被java反射攻击3 *@throwsNoSuchMethodException4 *@throwsInvocationTargetException5 *@throwsIllegalAccessException6 *@throwsInstantiationException7 *@throwsSecurityException8 *@throwsIllegalArgumentException9 */
10 @Test11 public void testSingletonReflectAttack() throwsIllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException12 {13 System.out.println("-------------单例模式被java反射攻击测试--------------");14 SingletonReflectAttack.attack();15 System.out.println("--------------------------------------------------");16 }17
运行结果:
返回结果为false,说明创建了两个不同的实例。通过反射获取构造函数,然后调用setAccessible(true)就可以调用私有的构造函数;所以创建出来的两个实例时不同的对象。
如果要抵御这种攻击,就要修改构造器,让他在被要求创建第二个实例的时候抛出异常。
下面,我们对饿汉式单例模式做修改。
举例2.经过处理的单例类,JAVA反射机制攻击测试
SingletonNotAttackByReflect.java 代码清单【2.1】
1 packagecom.lxf.singleton;2
3 importjavax.management.RuntimeErrorException;4
5 public classSingletonNotAttackByReflect6 {7 private static boolean flag = false;8 private static final SingletonNotAttackByReflect INSTANCE = newSingletonNotAttackByReflect();9
10 //保证其不被java反射攻击
11 privateSingletonNotAttackByReflect()12 {13 synchronized (SingletonNotAttackByReflect.class)14 {15 if(false ==flag)16 {17 flag = !flag;18 }19 else
20 {21 throw new RuntimeException("单例模式正在被攻击");22 }23
24 }25 }26
27 public staticSingletonNotAttackByReflect getInstance()28 {29 returnINSTANCE;30 }31
32
33 }
SingletonReflectAttack.java 代码清单【2.2】
1 public static voidmodifiedByAttack()2 {3 try
4 {5 Class classType = SingletonNotAttackByReflect.class;6 Constructor constructor = classType.getDeclaredConstructor(null);7 constructor.setAccessible(true);8 SingletonNotAttackByReflect singleton =(SingletonNotAttackByReflect) constructor.newInstance();9 SingletonNotAttackByReflect singleton2 =SingletonNotAttackByReflect.getInstance();10
11 System.out.println(singleton ==singleton2);12 }13 catch(Exception e)14 {15 e.printStackTrace();16 }17
18 }
SingletonReflectAttackMain.java 代码清单【2.3】
1 /**
2 * 2.修改后的单例模式被java反射攻击测试.3 * 攻击失败4 *@throwsIllegalArgumentException5 *@throwsSecurityException6 *@throwsInstantiationException7 *@throwsIllegalAccessException8 *@throwsInvocationTargetException9 *@throwsNoSuchMethodException10 */
11
12 @Test13 public void testModifiedByattack() throwsIllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException14 {15 System.out.println("-------------修改后的单例模式被java反射攻击测试--------------");16 SingletonReflectAttack.modifiedByAttack();17 System.out.println("----------------------------------------------------------");18 }
运行结果:
在之前,我们也介绍过,枚举类型的单例模式也可以防止被JAVA反射攻击,这里我们简单测试一下。
举例3:枚举类型的单例模式被JAVA反射机制攻击测试
Singleton6.java 代码清单【3.1】
1 public enumSingleton62 {3 INSTANCE;4
5 privateResource instance;6
7 Singleton6()8 {9 instance = newResource();10 }11
12 publicResource getInstance()13 {14 returninstance;15 }16
17
18 }
SingletonReflectAttack.java 代码清单【3.2】
1 public static void enumAttack() throwsSecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException2 {3 try
4 {5 Class classType = Singleton6.class;6 Constructor constructor =(Constructor) classType.getDeclaredConstructor();7 constructor.setAccessible(true);8 constructor.newInstance();9
10 }11 catch(Exception e)12 {13 e.printStackTrace();14 }
SingletonReflectAttackMain.java 代码清单【3.3】
1 /**
2 * 枚举类型的单例模式被java反射攻击测试3 * 攻击失败4 *5 *@throwsIllegalArgumentException6 *@throwsSecurityException7 *@throwsInstantiationException8 *@throwsIllegalAccessException9 *@throwsInvocationTargetException10 *@throwsNoSuchMethodException11 */
12
13 @Test14 public void testenumAttack() throwsIllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException15 {16 System.out.println("-------------枚举类型的单例模式被java反射攻击测试--------------");17 SingletonReflectAttack.enumAttack();18 System.out.println("----------------------------------------------------------");19 }
运行结果:
4.总结与拓展
所以,在项目开发中,我们要根据实际情况,选择最安全的单例模式实现方式。