詳解Java序列化如何破壞單例模式
先看代碼:
public class Singleton { private Singleton(){} private static class SingletonInstance{ private static final Singleton instance = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.instance; } }
我想應(yīng)該沒有不知道這行個類是干嘛的小伙伴了吧,這是單例模式的一種寫法。
單例模式是每一個 Java boy 必須要掌握的設(shè)計模式,它所描述的是在某個進程內(nèi),某個類有且僅有一個實例。我們知道要破壞單例模式就必須讓它創(chuàng)建多個對象。創(chuàng)建對象的方式無非就幾種:
- new
- clone
- 反射
- 反序列化
首先單例模式的構(gòu)造器一定是 private 的,所以 new 這種方式是無法破壞單例模式的。 而 clone 需要實現(xiàn) Cloneable 接口,單例模式誰如果實現(xiàn)了這個接口,請打死它。所以就剩下反射和反序列化了。本篇文章只討論反序列化。
反序列化破壞單例模式
與 clone 方式一樣,反序列化需要實現(xiàn) Serializable 接口,但是有小伙伴可能會說,誰會在單例模式中實現(xiàn) Serializable 接口咯,除非他瘋了,確實是這種情況,但是在實際情況中它并不是一定會避免的,有些類它就是一定要序列化。比如單例對象在不同環(huán)境或應(yīng)用實例之間的共享、持久化或狀態(tài)恢復(fù),當(dāng)然這些場景都屬于比較特殊的場景。
繼續(xù)用上面例子:
public class SerializableSingleton implements Serializable { // 省略部分代碼 }
然后在對該類進行序列化和反序列化
public class Test { public static void main(String[] args) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.txt")); SerializableSingleton singleton = SerializableSingleton.getInstance(); oos.writeObject(singleton); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Singleton.txt")); SerializableSingleton singleton1 = (SerializableSingleton) ois.readObject(); System.out.println("singleton = singleton1:" + (singleton == singleton1)); } }
運行結(jié)果
singleton = singleton1:false
通過對 Singleton 進行反序列化得到了一個全新的對象,這就破壞了 Singleton 的單例性了。我們看 readObject()
源碼就知道了。
public final Object readObject() throws IOException, ClassNotFoundException { //... int outerHandle = passHandle; try { Object obj = readObject0(false); //... return obj; } finally { //... } }
調(diào)用 readObject0()
:
private Object readObject0(boolean unshared) throws IOException { // ... try { switch (tc) { // ... case TC_OBJECT: // readObject0() return checkResolve(readOrdinaryObject(unshared)); // ... } } finally { depth--; bin.setBlockDataMode(oldMode); } }
readObject0()
是根據(jù)反序列化對象的不同執(zhí)行不同的方法來反序列化一個實例對象。我們這里是 Object,所以進一步看 readOrdinaryObject()
。
private Object readOrdinaryObject(boolean unshared) throws IOException { // ... Object obj; try { // 核心代碼 // 反射創(chuàng)建一個新對象 obj = desc.isInstantiable() ? desc.newInstance() : null; } catch (Exception ex) { throw (IOException) new InvalidClassException( desc.forClass().getName(), "unable to create instance").initCause(ex); } // ... if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep); } } return obj; }
這段代碼最核心的地方就是:
obj = desc.isInstantiable() ? desc.newInstance() : null;
底層依然是利用反射的方式來創(chuàng)建一個新對象。
那么對于這種方式有什么保護措施沒?在 readOrdinaryObject()
最后面一段就已經(jīng)告知了:
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { //.... }
判斷反序列化的類是否已實現(xiàn)了 readResolve()
,如果有則會調(diào)用該方法,我們只需要在該方法里面返回原對象就可以了。驗證下。
public class SerializableSingleton implements Serializable { // ... private Object readResolve() { return SingletonInstance.instance; } }
執(zhí)行結(jié)果:
singleton = singleton1:true
執(zhí)行結(jié)果為 true,就說明序列化和反序列化出來的是同一個對象。
所以,要想防止單例被反序列化破壞,就讓單例實現(xiàn) readResolve()
方法,返回同一個對象即可。
到此這篇關(guān)于詳解Java序列化如何破壞單例模式的文章就介紹到這了,更多相關(guān)Java序列化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java進行反編譯生成.java文件方式(javap、jad下載安裝使用)
這篇文章主要介紹了Java進行反編譯生成.java文件方式(javap、jad下載安裝使用),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12Java實現(xiàn)Map遍歷key-value的四種方法
本文主要介紹了Java實現(xiàn)Map遍歷key-value的四種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07JAVA根據(jù)ip地址獲取歸屬地的實現(xiàn)方法
本文主要介紹了JAVA根據(jù)ip地址獲取歸屬地的實現(xiàn)方法,要通過Java程序獲取IP地址對應(yīng)的城市,需要借助第三方的IP地址庫,下面就來介紹一下,感興趣的可以了解一下2023-10-10spring cloud實現(xiàn)Eureka注冊中心的HA的方法
本篇文章主要介紹了spring cloud實現(xiàn)Eureka注冊中心的HA的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01