在我的上篇隨筆中,我們知道了創建單例類有以下幾種方式:
(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 class Singleton 2 { 3 private static boolean flag = true; 4 private static final Singleton INSTANCE = new Singleton(); 5 6 private Singleton() 7 { 8 } 9 10 public static Singleton newInstance() 11 { 12 return INSTANCE; 13 } 14 15 }
SingletonReflectAttack.java 代碼清單【1.2】
1 /** 2 * 單例模式被java反射攻擊 3 * @throws IllegalArgumentException 4 * @throws InstantiationException 5 * @throws IllegalAccessException 6 * @throws InvocationTargetException 7 * @throws SecurityException 8 * @throws NoSuchMethodException 9 */ 10 11 public static void attack() throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, SecurityException, NoSuchMethodException 12 { 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 * @throws NoSuchMethodException 4 * @throws InvocationTargetException 5 * @throws IllegalAccessException 6 * @throws InstantiationException 7 * @throws SecurityException 8 * @throws IllegalArgumentException 9 */ 10 @Test 11 public void testSingletonReflectAttack() throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException 12 { 13 System.out.println("-------------單例模式被java反射攻擊測試--------------"); 14 SingletonReflectAttack.attack(); 15 System.out.println("--------------------------------------------------"); 16 } 17
運行結果:

返回結果為false,說明創建了兩個不同的實例。通過反射獲取構造函數,然后調用setAccessible(true)就可以調用私有的構造函數;所以創建出來的兩個實例時不同的對象。
如果要抵御這種攻擊,就要修改構造器,讓他在被要求創建第二個實例的時候拋出異常。
下面,我們對餓漢式單例模式做修改。
舉例2.經過處理的單例類,JAVA反射機制攻擊測試
SingletonNotAttackByReflect.java 代碼清單【2.1】
1 package com.lxf.singleton; 2 3 import javax.management.RuntimeErrorException; 4 5 public class SingletonNotAttackByReflect 6 { 7 private static boolean flag = false; 8 private static final SingletonNotAttackByReflect INSTANCE = new SingletonNotAttackByReflect(); 9 10 //保證其不被java反射攻擊 11 private SingletonNotAttackByReflect() 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 static SingletonNotAttackByReflect getInstance() 28 { 29 return INSTANCE; 30 } 31 32 33 }
SingletonReflectAttack.java 代碼清單【2.2】
1 public static void modifiedByAttack() 2 { 3 try 4 { 5 Class<SingletonNotAttackByReflect> classType = SingletonNotAttackByReflect.class; 6 Constructor<SingletonNotAttackByReflect> 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 * @throws IllegalArgumentException 5 * @throws SecurityException 6 * @throws InstantiationException 7 * @throws IllegalAccessException 8 * @throws InvocationTargetException 9 * @throws NoSuchMethodException 10 */ 11 12 @Test 13 public void testModifiedByattack() throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException 14 { 15 System.out.println("-------------修改后的單例模式被java反射攻擊測試--------------"); 16 SingletonReflectAttack.modifiedByAttack(); 17 System.out.println("----------------------------------------------------------"); 18 }
運行結果:


在之前,我們也介紹過,枚舉類型的單例模式也可以防止被JAVA反射攻擊,這里我們簡單測試一下。
舉例3:枚舉類型的單例模式被JAVA反射機制攻擊測試
Singleton6.java 代碼清單【3.1】
1 public enum Singleton6 2 { 3 INSTANCE; 4 5 private Resource instance; 6 7 Singleton6() 8 { 9 instance = new Resource(); 10 } 11 12 public Resource getInstance() 13 { 14 return instance; 15 } 16 17 18 }
SingletonReflectAttack.java 代碼清單【3.2】
1 public static void enumAttack() throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException 2 { 3 try 4 { 5 Class<Singleton6> classType = Singleton6.class; 6 Constructor<Singleton6> constructor =(Constructor<Singleton6>) 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 * @throws IllegalArgumentException 6 * @throws SecurityException 7 * @throws InstantiationException 8 * @throws IllegalAccessException 9 * @throws InvocationTargetException 10 * @throws NoSuchMethodException 11 */ 12 13 @Test 14 public void testenumAttack() throws IllegalArgumentException, SecurityException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException 15 { 16 System.out.println("-------------枚舉類型的單例模式被java反射攻擊測試--------------"); 17 SingletonReflectAttack.enumAttack(); 18 System.out.println("----------------------------------------------------------"); 19 }
運行結果:

4.總結與拓展
所以,在項目開發中,我們要根據實際情況,選擇最安全的單例模式實現方式。
