天天看點

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

單例模式實際上也不止 7 種。但是,每一種都并非安全的。今天我給大家講一講如何利用克隆、序列化、反射機制破壞單例模式。

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

我今天以癡漢式單例為例來講,其他的單例模式破壞方式類似。

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

上面這個單例實作,看似很完美。但我們通過克隆、序列化、反射機制,來擊破這個單例模式。

建立一個 Java 對象一般有 4 種方式:new 、克隆、序列化、反射!現在 new 這種方式不能使用了,那我們還可以使用剩下的 3 種方式!

先來看克隆!

實作 Cloneable 接口,盡管構造函數是私有,但還會建立一個對象。因為 clone 方法不會調用構造函數,會直接從記憶體中 copy 記憶體區域。是以單例模式的類是切記不要實作 Cloneable 接口。

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

自己運作一下,hash 值不一樣,是以克隆成功了,生成了一個新對象。單例模式被成功破壞!

那麼怎麼抵制被克隆呢?

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

就是重寫 clone 方法,調用 getInstance() 方法,傳回已有的執行個體即可!

現在我們再來看序列化是如何破壞單例模式的。現在假設你的單例模式,實作了 Serializable 接口。看我下面反序列化的案例!

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

執行之後,hash 值不一樣了,擷取的對象非同一對象。結論,單例模式又被破壞了!那麼怎麼防止被反序列化呢?

很簡單,自定義實作對象的 readResolve() 方法。

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

為什麼實作對象的 readResolve() 方法就可以了呢?這個你可以自己 debug 一下,上面反序列化的代碼。其中有一個 readOrdinaryObject 方法在做怪!

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

關鍵代碼都注射的比較全,我相信你能看明白。如果還不明白,加我微信ID:xttblog。

最後,我們再來看反射是如何破壞單例模式的!

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

執行之後,hash 值不一樣了,擷取的對象非同一對象。結論,單例模式又被破壞了!那麼如何解決呢?很簡單,加入下面的代碼。

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

因為執行反射會調用無參構造函數,是以上面的判斷就可以起作用了!

綜上所述,單例模式需要考慮,線程安全問題,效率問題,防止反射、反序列化、克隆。要不然,就有可能被黑客利用!

看到這裡,有些人可能會問,這也太麻煩了,有沒有更簡便的方法呢?有,枚舉模式。枚舉類型是絕對單例的,可以無責任使用。

滅霸所有單例模式,克隆、序列化、反射機制破壞7種單例模式

一個枚舉,就算實作雙接口,也是無論如何都無法被破壞的。枚舉無法克隆,沒有這樣的方法。沒有構造函數,會抛出異常。就算你在枚舉裡加了構造函數,也是一樣的。對于反序列化 Java 僅僅是将枚舉對象的 name 屬性輸出到結果中,反序列化的時候則是通過 java.lang.Enum 的 valueOf 方法來根據名字查找枚舉對象。同時,編譯器是不允許任何對這種序列化機制的定制的,是以禁用了 writeObject、readObject、readObjectNoData、writeReplace 和 readResolve 等方法。是以,枚舉才是實作單例模式的最好方式!