通過一系列演進挥下,現(xiàn)在我們可以確定改造后的單例模式在系統(tǒng)運行過程中只會產(chǎn)生一個單例對象。但是這是不是堅不可摧的呢桨醋?實際上有兩種方式會破壞這種單例模式棚瘟。
序列號和反序列化
現(xiàn)在以餓漢模式為例,寫序列號和反序列化的測試代碼喜最。
執(zhí)行的結(jié)果如下偎蘸,從結(jié)果中可以看出,序列化和反序列化后拿到的對象與原先不是同一個對象,這就違背了單例模式的初衷迷雪。
為了解決這個問題限书,則只需要在這個類中加入以下方法。添加readResolve()方法章咧,直接返回實例蔗包。
再次執(zhí)行測試方法,得到以下結(jié)果慧邮。說明加了readResolve方法之后,序列號與反序列化拿到的對象和原先的是同一個對象了舟陆。
readResolve()方法并不是Object聲明的误澳,而且為什么一定要取這個名字呢?我們可以從ObjectInputStream的源碼中找到原因秦躯。首先看到忆谓,反序列化后獲取對象的方法ois.readObject(),查看readObject()的源碼踱承。
readObject()方法返回的obj是通過readObject0()方法得來的倡缠。再看readObject0()進行了什么操作。
這個方法中有一段是switch/case匹配茎活,我們需要返回的是對象昙沦,所以現(xiàn)在只需要關(guān)注這邊的checkResolve()方法。查看該方法载荔,其中如下段落:
從這段代碼可以看出盾饮,這邊會判斷實例所屬類是否有readResolve()方法。如果有懒熙,則執(zhí)行丘损,返回的是原實例,最后返回該實例工扎。所以最終徘钥,在單例類中加上readResolve()方法返回實例,就可以防止序列化與反序列化破壞單例模式肢娘。具體的可以自己查看源碼呈础,查看反序列化重新獲取實例的過程。
反射攻擊
現(xiàn)在再寫一個通過反射拿到新實例對象的測試方法蔬浙。
該方法中顯示在27行拿到該類的構(gòu)造器猪落。由于該類構(gòu)造器私有化,則在28行獲取構(gòu)造器權(quán)限畴博。通過構(gòu)造器拿到一個實例和通過getInstance()方法拿到的實例進行對比笨忌,結(jié)果是通過反射會創(chuàng)建新的實例。
解決方法也比較簡單俱病,只需要改造私有化的構(gòu)造器官疲,如下袱结。調(diào)用構(gòu)造器的時候進行判斷,拋出異常途凫。
驗證一下垢夹,如預(yù)期拋出了異常。
這樣就能解決反射調(diào)用私有構(gòu)造器帶來的單例模式破壞了维费。