單例模式與反射的博弈
1. 單例模式介紹
單例模式的核心概念是:私有化構造器,私有化靜態(tài)對象屬性,對外公開獲取對象屬性的方法,
從而使得外部類引用該類時,只存在唯一的一個對象。
2. 餓漢式單例模式代碼
- 餓漢式是最簡單的一種實現方式,但是失去了 lazy loading (懶加載)的特性,被 final 和 static 同時修飾的屬性會在類的準備階段完成賦值
public class Singleton_1 {
// 1. 私有化構造器
private Singleton_1() {}
// 2. 本類內部創(chuàng)建靜態(tài)常量型實例
private final static Singleton_1 instance = new Singleton_1();
// 3. 對外提供公有的獲取實例方法
public static Singleton_1 getInstance() {
return instance;
}
}
3. 使用反射獲取私有化構造器破解單例
// 正常方式獲得的對象
Singleton_1 instance = Singleton_1.getInstance();
// 獲得class 對象
Class<? extends Singleton_1> singleton_class = instance.getClass();
Constructor<? extends Singleton_1> constructor = singleton_class.getDeclaredConstructor(); // 獲取無參構造器
constructor.setAccessible(true); // 給予私有構造器的使用權限
// 使用構造器創(chuàng)建新的實例
Singleton_1 singleton_1 = constructor.newInstance();
System.out.println("創(chuàng)建新實例成功,破解成功...");
System.out.println(instance.hashCode());
System.out.println(singleton_1.hashCode());
System.out.println(instance == singleton_1); // 對象比較
- 輸出結果如下:

反射破解單例.jpg
4. 單例模式在構造器中加入驗證防止反射使用構造器
代碼如下:
// 防止反射破壞
private static boolean flag = true;
// 1. 私有化構造器
private Singleton_1() {
if (flag) {
flag = false; // 在第一次構造完實例后將不能使用
} else {
throw new RuntimeException("單例模式遇到攻擊,第二個對象未創(chuàng)建成功");
}
}
輸出如下:

單例驗證構造器.jpg
5. 反射修改flag驗證
代碼如下:
Field flag = singleton_class.getDeclaredField("flag");
flag.setAccessible(true);
System.out.println(flag.get(instance));
flag.set(instance, true); // 修改flag值為true
System.out.println(flag.get(instance));
在修改完flag屬性后,依舊能夠破解單例模式。而Enum(枚舉)獨有的一些特性讓反射不能夠使用私有構造器去創(chuàng)建新的實例,因此推薦使用Enum來設計單例模式
6. Enum單例
- 簡潔好用
public enum SingletonEnum {
INSTANCE;
public void sayOK() {
System.out.println("ok~");
}
}
- 測試代碼
class Test_Enum {
public static void main(String[] args) throws Exception{
SingletonEnum instance_1 = SingletonEnum.INSTANCE;
SingletonEnum instance_2 = SingletonEnum.INSTANCE;
System.out.println("正常情況下,兩個對象是否相同? " + (instance_1 == instance_2));
// 使用反射
Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingletonEnum newInstance = constructor.newInstance();
System.out.println("使用反射,能否創(chuàng)建不同實例?" + (instance_1 == newInstance));
}
}
- 輸出結果

枚舉單例測試.jpg
結果是直接報錯,因為Enum(枚舉)并沒有無參構造器~ 不信就看下面代碼
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
private final String name;
public final String name() {
return name;
}
private final int ordinal;
public final int ordinal() {
return ordinal;
}
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
定義枚舉相當于自動繼承了Enum類,而Enum類本身只有一個構造器,那就是 protected Enum(String name, int ordinal), 所以我們獲取不到無參構造器。
那么用有參構造器會怎么樣?
class Test_Enum {
public static void main(String[] args) throws Exception{
SingletonEnum instance_1 = SingletonEnum.INSTANCE;
SingletonEnum instance_2 = SingletonEnum.INSTANCE;
System.out.println("正常情況下,兩個對象是否相同? " + (instance_1 == instance_2));
// 拿enum定義的唯一的構造器
Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
SingletonEnum newInstance = constructor.newInstance("Test1", 11);
System.out.println("使用反射,能否創(chuàng)建不同實例?" + (instance_1 == newInstance));
}
}
- 輸出結果

反射枚舉失敗.jpg
通過第二行異常點進去看到源碼
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
****省略****
if ((clazz.getModifiers() & Modifier.ENUM) != 0) // 如果反射的類被Enum修飾,直接拋異常
throw new IllegalArgumentException("Cannot reflectively create enum objects");
****省略****
}