單例模式與反射的博弈

單例模式與反射的博弈

1. 單例模式介紹

單例模式的核心概念是:私有化構(gòu)造器扳埂,私有化靜態(tài)對(duì)象屬性业簿,對(duì)外公開(kāi)獲取對(duì)象屬性的方法,

從而使得外部類引用該類時(shí)阳懂,只存在唯一的一個(gè)對(duì)象梅尤。

2. 餓漢式單例模式代碼

  • 餓漢式是最簡(jiǎn)單的一種實(shí)現(xiàn)方式,但是失去了 lazy loading (懶加載)的特性岩调,被 final 和 static 同時(shí)修飾的屬性會(huì)在類的準(zhǔn)備階段完成賦值
public class Singleton_1 {

    // 1. 私有化構(gòu)造器
    private Singleton_1() {}

    // 2. 本類內(nèi)部創(chuàng)建靜態(tài)常量型實(shí)例
    private final static Singleton_1 instance = new Singleton_1();

    // 3. 對(duì)外提供公有的獲取實(shí)例方法
    public static Singleton_1 getInstance() {
        return instance;
    }

}

3. 使用反射獲取私有化構(gòu)造器破解單例

        // 正常方式獲得的對(duì)象
        Singleton_1 instance = Singleton_1.getInstance();
        // 獲得class 對(duì)象
        Class<? extends Singleton_1> singleton_class = instance.getClass();
        Constructor<? extends Singleton_1> constructor = singleton_class.getDeclaredConstructor(); // 獲取無(wú)參構(gòu)造器
        constructor.setAccessible(true); // 給予私有構(gòu)造器的使用權(quán)限
      
        // 使用構(gòu)造器創(chuàng)建新的實(shí)例
        Singleton_1 singleton_1 = constructor.newInstance();
        System.out.println("創(chuàng)建新實(shí)例成功巷燥,破解成功...");
        System.out.println(instance.hashCode());
        System.out.println(singleton_1.hashCode());
        System.out.println(instance == singleton_1); // 對(duì)象比較
   
  • 輸出結(jié)果如下:
反射破解單例.jpg

4. 單例模式在構(gòu)造器中加入驗(yàn)證防止反射使用構(gòu)造器

代碼如下:

    // 防止反射破壞
    private static boolean flag = true;

    // 1. 私有化構(gòu)造器
    private Singleton_1() {
        if (flag) {
            flag = false; // 在第一次構(gòu)造完實(shí)例后將不能使用
        } else {
            throw new RuntimeException("單例模式遇到攻擊,第二個(gè)對(duì)象未創(chuàng)建成功");
        }
    }

輸出如下:


單例驗(yàn)證構(gòu)造器.jpg

5. 反射修改flag驗(yàn)證

代碼如下:

        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(枚舉)獨(dú)有的一些特性讓反射不能夠使用私有構(gòu)造器去創(chuàng)建新的實(shí)例,因此推薦使用Enum來(lái)設(shè)計(jì)單例模式

6. Enum單例

  • 簡(jiǎn)潔好用
public enum SingletonEnum {
    INSTANCE;
    public void sayOK() {
        System.out.println("ok~");
    }
}
  • 測(cè)試代碼
class Test_Enum {
    public static void main(String[] args) throws Exception{
        SingletonEnum instance_1 = SingletonEnum.INSTANCE;
        SingletonEnum instance_2 = SingletonEnum.INSTANCE;
        System.out.println("正常情況下葱淳,兩個(gè)對(duì)象是否相同? " + (instance_1 == instance_2));

        // 使用反射
        Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        SingletonEnum newInstance = constructor.newInstance();
        System.out.println("使用反射钝腺,能否創(chuàng)建不同實(shí)例?" + (instance_1 == newInstance));
    }
}
  • 輸出結(jié)果
枚舉單例測(cè)試.jpg
結(jié)果是直接報(bào)錯(cuò),因?yàn)镋num(枚舉)并沒(méi)有無(wú)參構(gòu)造器~ 不信就看下面代碼
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;
    }

定義枚舉相當(dāng)于自動(dòng)繼承了Enum類赞厕,而Enum類本身只有一個(gè)構(gòu)造器艳狐,那就是 protected Enum(String name, int ordinal), 所以我們獲取不到無(wú)參構(gòu)造器。

那么用有參構(gòu)造器會(huì)怎么樣皿桑?
class Test_Enum {
    public static void main(String[] args) throws Exception{
        SingletonEnum instance_1 = SingletonEnum.INSTANCE;
        SingletonEnum instance_2 = SingletonEnum.INSTANCE;
        System.out.println("正常情況下僵驰,兩個(gè)對(duì)象是否相同? " + (instance_1 == instance_2));
        // 拿enum定義的唯一的構(gòu)造器
        Constructor<SingletonEnum> constructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        SingletonEnum newInstance = constructor.newInstance("Test1", 11);
        System.out.println("使用反射,能否創(chuàng)建不同實(shí)例?" + (instance_1 == newInstance));
    }
}
  • 輸出結(jié)果
反射枚舉失敗.jpg
通過(guò)第二行異常點(diǎn)進(jìn)去看到源碼
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");
        ****省略****
 } 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蒜茴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子浆西,更是在濱河造成了極大的恐慌粉私,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件近零,死亡現(xiàn)場(chǎng)離奇詭異诺核,居然都是意外死亡抄肖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門窖杀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)漓摩,“玉大人,你說(shuō)我怎么就攤上這事入客」鼙校” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵桌硫,是天一觀的道長(zhǎng)夭咬。 經(jīng)常有香客問(wèn)我,道長(zhǎng)铆隘,這世上最難降的妖魔是什么卓舵? 我笑而不...
    開(kāi)封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮膀钠,結(jié)果婚禮上掏湾,老公的妹妹穿的比我還像新娘。我一直安慰自己肿嘲,他們只是感情好融击,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著睦刃,像睡著了一般砚嘴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上涩拙,一...
    開(kāi)封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天际长,我揣著相機(jī)與錄音,去河邊找鬼兴泥。 笑死工育,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的搓彻。 我是一名探鬼主播如绸,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼旭贬!你這毒婦竟也來(lái)了怔接?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤稀轨,失蹤者是張志新(化名)和其女友劉穎扼脐,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奋刽,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓦侮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年艰赞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肚吏。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡方妖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出罚攀,到底是詐尸還是另有隱情党觅,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布坞生,位于F島的核電站仔役,受9級(jí)特大地震影響掷伙,放射性物質(zhì)發(fā)生泄漏是己。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一任柜、第九天 我趴在偏房一處隱蔽的房頂上張望卒废。 院中可真熱鬧,春花似錦宙地、人聲如沸摔认。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)参袱。三九已至,卻和暖如春秽梅,著一層夾襖步出監(jiān)牢的瞬間抹蚀,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工企垦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留环壤,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓钞诡,卻偏偏與公主長(zhǎng)得像郑现,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子荧降,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容