單例模式中的反射攻擊和反序列化問題

反射攻擊和反序列化問題是兩種最常見的破壞單例模式的手段剥槐,前者利用反射機制修改構(gòu)造函數(shù)的可見性然后強行創(chuàng)建一個新的實例宪摧,后者是將原先的對象序列化后再反序列化從而生成一個新的實例

反射攻擊

產(chǎn)生原因

單例模式實現(xiàn)的一個關(guān)鍵點就是構(gòu)造函數(shù)的私有性,反射攻擊利用反射機制獲取并修改構(gòu)造函數(shù)的可見性蕊苗,從而可以創(chuàng)建出新的實例孩革,破壞單例:

public class Reflection {

    public static void normalReflection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        \\利用反射獲取構(gòu)造函數(shù)
        Constructor c = StaticInnerClassSingleton.class.getDeclaredConstructor();
        \\改變構(gòu)造函數(shù)的可見性
        c.setAccessible(true);
        \\調(diào)用newInstance創(chuàng)建新的實例
        StaticInnerClassSingleton o1 = (StaticInnerClassSingleton) c.newInstance();
        \\通過getInstance方法獲取單例
        StaticInnerClassSingleton o2 = StaticInnerClassSingleton.getInstance();
        System.out.println(o1);
        System.out.println(o2);
    }

運行結(jié)果

design.Singleton.StaticInnerClassSingleton@1d44bcfa
design.Singleton.StaticInnerClassSingleton@266474c2
Process finished with exit code 0

通過運行結(jié)果可以發(fā)現(xiàn),兩個實例不是同一個熔掺,單例模式被破壞非剃。

枚舉防止反射攻擊

JDK不允許通過反射創(chuàng)建新的枚舉實例,所以枚舉單例不會被反射攻擊破壞:

    public static void enumReflection() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor c = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
        c.setAccessible(true);
        EnumSingleton o1 = (EnumSingleton) c.newInstance();
        EnumSingleton o2 = EnumSingleton.getInstance();
        System.out.println(o1);
        System.out.println(o2);

    }

運行結(jié)果:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at design.Singleton.Reflection.enumReflection(Reflection.java:22)
    at design.Singleton.Reflection.main(Reflection.java:30)

Process finished with exit code 1

反序列化攻擊

產(chǎn)生原因

對象序列化再反序列化時券坞,會生成一個新的對象恨锚,從而破壞單例:

public static void normalSingletonDeserializeProblem() throws IOException, ClassNotFoundException {
    StaticInnerclassSingleton o1 = StaticInnerclassSingleton.getInstance();
    //通過writeObject將對象序列化到文件
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
    oos.writeObject(o1);

    //讀取文件倍靡,反序列化
    File file = new File("tempFile");
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    StaticInnerclassSingleton newInstance = (StaticInnerclassSingleton) ois.readObject();

    System.out.println(o1);
    System.out.println(newInstance);
    System.out.println(StaticInnerclassSingleton.getInstance());
}

運行結(jié)果:

design.Singleton.StaticInnerclassSingleton@63947c6b
design.Singleton.StaticInnerclassSingleton@34a245ab
design.Singleton.StaticInnerclassSingleton@63947c6b

Process finished with exit code 0

反序列化之后,生成了一個新的對象他挎,單例被破壞

解決方法

通過實現(xiàn)一個readResolve方法捡需,可以避免反序列化問題

class NoProblemSingleton implements Serializable {
    private NoProblemSingleton() {}

    public static NoProblemSingleton getInstance() {
        return InnerHolder.INSTANCE;
    }

    private static class InnerHolder {
        private static final NoProblemSingleton INSTANCE = new NoProblemSingleton();
    }

    //實現(xiàn)readResolve方法可以解決反序列化攻擊,反序列化時會檢查有沒有這個方法呢撞,如果有可以調(diào)用這個方法返回對象
    private Object readResolve() {
        return InnerHolder.INSTANCE;
    }
}

運行結(jié)果:

design.Singleton.NoProblemSingleton@63947c6b
design.Singleton.NoProblemSingleton@63947c6b
design.Singleton.NoProblemSingleton@63947c6b
true

Process finished with exit code 0

實現(xiàn)readResolve方法后庵寞,反序列化的對象和newInstance返回的對象是同一個

原理

反序列化調(diào)用readObject方法時,會判斷類中是否有readResolve方法捐川。如果有逸尖,會調(diào)用readResolve方法并最終用readResolve方法返回的對象替換反序列化出來的對象。不過替換是發(fā)生在反序列化之后的岩齿,也就是說反序列化的對象還是生成了苞俘,只不過無法被訪問只能等待GC回收。

枚舉類型防止反序列化攻擊

枚舉單例不會有反序列化問題乞封,因為readObject如果判斷反序列化的對象是枚舉類型,會直接調(diào)用Enum.valueOf方法返回枚舉實例

總結(jié)

  1. 反射攻擊和反序列化是單例模式的兩個最常見的問題
  2. 在單例類中實現(xiàn)readResolve方法可以避免反序列化問題
  3. 枚舉既不存在反射問題肃晚,也不存在反序列化問題
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市拧廊,隨后出現(xiàn)的幾起案子晋修,更是在濱河造成了極大的恐慌,老刑警劉巖滤港,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溅漾,死亡現(xiàn)場離奇詭異,居然都是意外死亡添履,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門暮胧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來往衷,“玉大人严卖,你說我怎么就攤上這事∠剩” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵福铅,是天一觀的道長滑黔。 經(jīng)常有香客問我,道長略荡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任秧了,我火速辦了婚禮序无,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晶通。我一直安慰自己哟玷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布喉脖。 她就那樣靜靜地躺著抑月,像睡著了一般。 火紅的嫁衣襯著肌膚如雪题诵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天性锭,我揣著相機與錄音草冈,去河邊找鬼。 笑死臭家,一個胖子當(dāng)著我的面吹牛疲陕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播携茂,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼带膜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起式廷,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤芭挽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后袜爪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡俺陋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年腊状,在試婚紗的時候發(fā)現(xiàn)自己被綠了苔可。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡醇疼,死狀恐怖法焰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乙濒,我是刑警寧澤卵蛉,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站甘有,受9級特大地震影響葡缰,放射性物質(zhì)發(fā)生泄漏忱反。R本人自食惡果不足惜滤愕,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望间影。 院中可真熱鬧,春花似錦巩割、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至萝嘁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酸钦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工卑硫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚕断,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓硝拧,卻偏偏與公主長得像葛假,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子聊训,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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