序列化与反序列化的单例模式_序列化代理模式
序列化與反序列化的單例模式
在上一篇文章中 ,我談到了一般的序列化。 這是更加集中的內(nèi)容,并提供了一個(gè)細(xì)節(jié): 序列化代理模式 。 這是處理序列化中許多問題的一種好方法,通常是最好的方法。 如果開發(fā)人員只想了解這一主題,我會(huì)告訴他。
總覽
這篇文章的重點(diǎn)是在給出兩個(gè)簡短的示例之前,最后介紹模式的詳細(xì)定義,最后討論其優(yōu)缺點(diǎn)。
據(jù)我所知,該模式首先在約書亞·布洛赫(Joshua Bloch)的出色著作《 有效的Java》 (第1版:第57條;第2版:第78條 )中定義。 這篇文章主要重申了那里的說法。
本文中使用的代碼示例來自我在GitHub上創(chuàng)建的演示項(xiàng)目 。 查看更多詳細(xì)信息!
序列化代理模式
此模式應(yīng)用于單個(gè)類,并定義其序列化機(jī)制。 為了更容易閱讀,以下文本將分別將該類或其實(shí)例稱為原始一個(gè)或多個(gè)實(shí)例。
序列化代理
顧名思義,模式的關(guān)鍵是序列化代理 。 它被寫入字節(jié)流,而不是原始實(shí)例。 反序列化之后,它將創(chuàng)建原始類的實(shí)例,該類將在對象圖中取代。
目的是設(shè)計(jì)代理,使其成為原始類的最佳邏輯表示形式 。
實(shí)作
SerializationProxy是原始類的靜態(tài)嵌套類。 它的所有字段均為final,唯一的構(gòu)造函數(shù)將原始實(shí)例作為唯一的參數(shù)。 它提取該實(shí)例狀態(tài)的邏輯表示并將其分配給自己的字段。 由于原始實(shí)例被認(rèn)為是“安全的”,因此無需進(jìn)行一致性檢查或防御性復(fù)制。
原始類和代理類都實(shí)現(xiàn)Serializable。 但是,由于前者實(shí)際上從未真正寫入流中,因此只有后者需要一個(gè)流唯一標(biāo)識(shí)符 (通常稱為串行版本UID )。
序列化
當(dāng)要對原始實(shí)例進(jìn)行序列化時(shí),可以通知序列化系統(tǒng)將代理寫入字節(jié)流。 為此,原始類必須實(shí)現(xiàn)以下方法:
用代理替換原始實(shí)例
private Object writeReplace() {return new SerializationProxy(this); }反序列化
在反序列化時(shí),必須反轉(zhuǎn)從原始實(shí)例到代理實(shí)例的轉(zhuǎn)換。 這是通過SerializationProxy中的以下方法實(shí)現(xiàn)的,該方法在成功實(shí)例SerializationProxy代理實(shí)例后被調(diào)用:
將代理轉(zhuǎn)換回原始實(shí)例
private Object readResolve() {// create an instance of the original class// in the state defined by the proxy's fields }創(chuàng)建原始類的實(shí)例將通過其常規(guī)API(例如,構(gòu)造函數(shù))完成。
人工字節(jié)流
由于writeReplace常規(guī)字節(jié)流將僅包含代理的編碼。 但是對于人工流卻并非如此! 它們可以包含原始實(shí)例的編碼,并且由于反序列化這些序列未??包括在模式中,因此它無法為這種情況提供任何保護(hù)措施。
實(shí)際上,對此類實(shí)例進(jìn)行反序列化實(shí)際上是不需要的,必須防止。 這可以通過讓原始類中的方法(在這種情況下被調(diào)用)拋出異常來完成:
防止直接反序列化原始實(shí)例
private void readObject(ObjectInputStream stream) throws InvalidObjectException {throw new InvalidObjectException("Proxy required."); }例子
以下示例是完整演示項(xiàng)目的摘錄。 它們只顯示多汁的部分,而忽略了一些細(xì)節(jié)(例如writeReplace和readObject )。
復(fù)數(shù)
一種簡單的情況是復(fù)數(shù)的一種不變類型,稱為ComplexNumber (驚奇!)。 出于本示例的考慮,它在其字段中存儲(chǔ)了坐標(biāo)以及極坐標(biāo)形式(據(jù)說是出于性能方面的考慮):
ComplexNumber –字段
private final double real; private final double imaginary; private final double magnitude; private final double angle;序列化代理看起來像這樣:
ComplexNumber.SerializationProxy
private static class SerializationProxy implements Serializable {private final double real;private final double imaginary;public SerializationProxy(ComplexNumber complexNumber) {this.real = complexNumber.real;this.imaginary = complexNumber.imaginary;}/*** After the proxy is deserialized, it invokes a static factory method* to create a 'ComplexNumber' "the regular way".*/private Object readResolve() {return ComplexNumber.fromCoordinates(real, imaginary);} }可以看出,代理不存儲(chǔ)極坐標(biāo)形式的值。 原因是它應(yīng)該捕獲最佳的邏輯表示形式。 并且由于只需要一對值(坐標(biāo)或極坐標(biāo)形式)即可創(chuàng)建另一個(gè),因此僅一個(gè)序列化了。 這樣可以防止存儲(chǔ)兩個(gè)對以實(shí)現(xiàn)更好的性能的實(shí)現(xiàn)細(xì)節(jié)通過序列化泄漏到公共API中。
請注意,原始類和代理中的所有字段均為最終字段。 還要注意靜態(tài)工廠方法的調(diào)用,從而無需進(jìn)行任何附加的有效性檢查。
實(shí)例緩存
InstanceCache是一個(gè)異構(gòu)類型安全的容器 ,它使用從類到其實(shí)例的映射作為后備數(shù)據(jù)結(jié)構(gòu):
InstanceCache –字段
private final ConcurrentMap<Class<?>, Object> cacheMap;由于映射可以包含任意類型,因此并非所有映射都必須可序列化。 該類的合同規(guī)定,足以存儲(chǔ)可序列化的類。 因此,有必要過濾地圖。 代理的優(yōu)點(diǎn)是它是所有此類代碼的單點(diǎn):
InstanceCache.SerializationProxy
private static class SerializationProxy implements Serializable {// array lists are serializableprivate final ArrayList<Serializable> serializableInstances;public SerializationProxy(InstanceCache cache) {serializableInstances = extractSerializableValues(cache);}private static ArrayList<Serializable> extractSerializableValues(InstanceCache cache) {return cache.cacheMap.values().stream().filter(instance -> instance instanceof Serializable).map(instance -> (Serializable) instance).collect(Collectors.toCollection(ArrayList::new));}/*** After the proxy is deserialized, it invokes a constructor to create* an 'InstanceCache' "the regular way".*/private Object readResolve() {return new InstanceCache(serializableInstances);}}利弊
序列化代理模式減輕了序列化系統(tǒng)的許多問題。 在大多數(shù)情況下,這是實(shí)現(xiàn)序列化的最佳選擇,并且應(yīng)該是實(shí)現(xiàn)序列化的默認(rèn)方法。
優(yōu)點(diǎn)
這些是優(yōu)點(diǎn):
減少語言外特征
該模式的主要優(yōu)點(diǎn)是它減少了序列化的語言外特征 。 這主要是通過使用類的公共API創(chuàng)建實(shí)例來實(shí)現(xiàn)的(請參見上面的SerializationProxy.readResolve )。 因此, 每次創(chuàng)建實(shí)例都要經(jīng)過構(gòu)造函數(shù),并且始終會(huì)執(zhí)行正確初始化實(shí)例所需的所有代碼。
這也意味著在反序列化期間不必顯式調(diào)用此類代碼,這可以防止其重復(fù)。
對最終字段沒有限制
由于反序列化實(shí)例是在其構(gòu)造函數(shù)中初始化的,因此此方法不限制哪些字段可以是最終字段(通常是使用自定義序列化形式的情況 )。
靈活的實(shí)例化
實(shí)際上,代理的readResolve不必返回與序列化類型相同的實(shí)例。 它也可以返回任何子類。
Bloch給出以下示例:
考慮EnumSet的情況。 此類沒有公共構(gòu)造函數(shù),只有靜態(tài)工廠。 從客戶端的角度來看,它們返回EnumSet實(shí)例,實(shí)際上,它們返回兩個(gè)子類之一,具體取決于基礎(chǔ)枚舉類型的大小。 如果基礎(chǔ)枚舉類型具有64個(gè)或更少的元素,則靜態(tài)工廠將返回RegularEnumSet ; 否則,它們返回JumboEnumSet 。
現(xiàn)在考慮一下,如果序列化其枚舉類型具有60個(gè)元素的枚舉集,然后向該枚舉類型添加另外五個(gè)元素,然后反序列化該枚舉集,會(huì)發(fā)生什么情況。 序列化時(shí)它是一個(gè)RegularEnumSet實(shí)例,但反序列化后最好是JumboEnumSet實(shí)例。
有效的Java,第二版:p。 314
代理模式使這個(gè)瑣碎的事情變得很簡單: readResolve僅返回匹配類型的實(shí)例。 (這僅在類型符合Liskov替換原理的情況下有效 。)
更高的安全性
它還極大地減少了防止用人工字節(jié)流進(jìn)行某些攻擊所需的額外思考和工作。 (假設(shè)構(gòu)造函數(shù)已正確實(shí)現(xiàn)。)
符合單一責(zé)任原則
序列化通常不是類的功能要求,但仍會(huì)極大地改變其實(shí)現(xiàn)方式。 這個(gè)問題無法消除,但至少可以通過更好地分工來減輕。 讓類做它的工作,然后讓代理處理序列化。 這意味著代理包含有關(guān)序列化的所有重要代碼,但僅包含其他內(nèi)容。
與SRP一樣 ,這大大提高了可讀性。 關(guān)于序列化的所有行為都可以在一個(gè)地方找到。 而且序列化的表單也更容易發(fā)現(xiàn),因?yàn)樵诖蠖鄶?shù)情況下,只需查看代理的字段即可。
缺點(diǎn)
Joshua Bloch描述了該模式的一些局限性。
不適合繼承
它與客戶端可擴(kuò)展的類不兼容。
有效的Java,第二版:p。 315
是的,就是這樣。 沒有進(jìn)一步的評論。 我不太了解這一點(diǎn),但是我會(huì)發(fā)現(xiàn)更多…
圓形對象圖的可能問題
它與某些對象圖包含圓度的類不兼容:如果嘗試從對象的序列化代理的readResolve方法中調(diào)用對象上的方法,則會(huì)得到ClassCastException ,因?yàn)槟€沒有對象,只有它序列化代理。
有效的Java,第二版:p。 315
性能
代理將構(gòu)造函數(shù)執(zhí)行添加到序列化和反序列化中。 布洛赫(Bloch)舉例說明,這臺(tái)機(jī)器的價(jià)格要貴14%。 當(dāng)然,這不是精確的度量,但是證實(shí)了那些構(gòu)造函數(shù)調(diào)用不是免費(fèi)的理論。
反射
我們已經(jīng)看到了序列化代理模式是如何定義和實(shí)現(xiàn)的,以及它的優(yōu)點(diǎn)和缺點(diǎn)。 應(yīng)該清楚的是,與默認(rèn)和自定義序列化相比,它具有一些主要優(yōu)點(diǎn),應(yīng)在適用時(shí)使用。
約書亞·布洛赫(Joshua Bloch)的最后一句話:
總之,每當(dāng)發(fā)現(xiàn)自己不得不在其客戶端無法擴(kuò)展的類上編寫readObject或writeObjet方法(用于自定義序列化形式)時(shí),請考慮序列化代理模式。 這種模式可能是用非平凡的不變變量穩(wěn)健地序列化對象的最簡單方法。
有效的Java,第二版:p。 315
翻譯自: https://www.javacodegeeks.com/2015/01/the-serialization-proxy-pattern.html
序列化與反序列化的單例模式
總結(jié)
以上是生活随笔為你收集整理的序列化与反序列化的单例模式_序列化代理模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓dhcp9是什么手机(安卓dhcp)
- 下一篇: 给oim_对OIM Web(UI)层进行