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