單例模式與反射的博弈

單例模式與反射的博弈

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");
        ****省略****
 } 
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容