《Effective Java》學習筆記 —— 序列化
Java的序列化API提供了一個框架,用來將物件編碼成一個位元組流(序列化,serializing),並從位元組流中重新建立物件(反序列化, deserializing)。
第74條 謹慎地實現Serializable介面
* 實現Serializable介面最大的代價是,一旦一個類被髮布,就大大降低了“改變這個類的實現”的靈活性。
* 實現Serializable介面的第二個代價是,它增加了出現Bug和安全漏洞的可能性。
* 實現Serializable介面的第三個代價是,隨著新版本的釋出,相關的測試負擔也增加了。
* 內部類(inner class)不應該實現Serializable介面,內部類的預設序列化形式是定義不清楚的。而靜態成員類(static member class)可以實現。
第75條 考慮使用自定義的序列化形式
* 如果所有的例項域都是瞬時的(transient),從技術角度而言,不呼叫 defaultWriteObject 和 defaultReadObject 也是允許的,但不推薦這麼做。
* 被標記為transient的域,如果是物件引用域,則被初始化為null;如果是數值基本域,則為0;如果是boolean域,則為false。如果這些初始值不能被任何transient域接受,則需要提供一個readObject方法,先呼叫defaultReadObject,然後再將其恢復為可接受的值。
* 宣告一個顯式的序列化版本id。
第76條 保護性的編寫 readObject 方法
* readObject方法實際上如同一個公有的構造器,如同其他構造器一樣,也要注意同樣的所有注意事項。
* 編寫健壯的readO方法:
-- 對於物件引用域必須保持為私有的類,要保護性的拷貝這些域中的每一個物件。
-- 對於任何約束條件,如果檢查失敗,則丟擲一個InvalidObjectException異常。這些檢查動作應該在跟在保護性拷貝之後。
-- 如果整個物件圖在被反序列化之後必須進行驗證,就應該使用ObjectInputValidation介面。
-- 無論是直接方式還是間接方式,都不要呼叫類中任何可被覆蓋的方法。
第78條 對於例項控制,列舉型別優先於readResolve
* 如果以下類實現Serializable 介面,那它將不再是單例:
1 public class Singleon { 2private static final Singleon INSTANCE = new Singleon(); 3 4private Singleon() { } 5 6// other codes 7 }
// 實現序列化介面後將不再是一個單例 public class Singleon implements Serializable { private static final Singleon INSTANCE = new Singleon(); private Singleon() { } // other codes }
readResolve特性允許通過readObject建立的例項代替另一個例項。對於一個正在被反序列化的物件,如果它的類定義了一個readResolve方法,並且具備正確的宣告,那麼在反序列化之後,新建物件上的readResolve方法就會被呼叫。然後該方法返回的引用將被返回,取代新建的物件。
如果上述的Singleon類實現了下面的方法,就足以保證它的單例屬性:
1private Object readResolve() { 2return INSTANCE; 3}
事實上,如果依賴readResolve進行例項控制,帶有物件引用型別的所有例項域都必須宣告為transient 。否則,攻擊者就有可能在readResolve方法被執行之前,保護指向反序列化的物件的引用。
* 將一個可序列化的例項受控的類編寫成列舉,則可以絕對保證除了宣告的常量之外,不會有別的例項。
第78條 考慮使用序列化代理代替序列化例項
* 序列化代理模式:
(1)首先,增加一個靜態內部類,其成員引數與需要序列化的外部類一致。
(2)然後,在外部類中新增writeReplace方法。修改序列化時寫入的內容,寫入代理類的內容,而不是外部類的。
(3)再在外部類新增readObject方法,讓其丟擲異常(由於寫入的時候不是外部類的,那讀的時候肯定也不是外部類的,如果是,則一定是偽造的)。
(4)最後,在內部類中增加一個readResolve 方法,返回一個外部類例項。
從整體上看,序列化代理模式就是增加一層轉換。寫入時先轉換為代理類再寫入,讀出時再將其轉換為被代理的類。
完整程式碼如下:
1 import java.io.InvalidObjectException; 2 import java.io.ObjectInputStream; 3 import java.io.Serializable; 4 import java.util.Date; 5 6 /** 7* @author Haoye 8* @brief 9* @detail 10* @date 2018/10/6 11* @see 12*/ 13 public class Period implements Serializable { 14private static final long serialVersionUID = 1; 15private final Date start; 16private final Date end; 17 18public Period(Date start, Date end) { 19this.start = start; 20this.end = end; 21} 22 23public Date getStart() { 24return start; 25} 26 27public Date getEnd() { 28return end; 29} 30 31/** 32* writeReplace for the serialization proxy pattern 33* @return instance of SerializationProxy 34*/ 35private Object writeReplace() { 36return new SerializationProxy(this); 37} 38 39private void readObject(ObjectInputStream inputStream) throws InvalidObjectException { 40throw new InvalidObjectException("Proxy required"); 41} 42 43/** 44* proxy class 45*/ 46private static class SerializationProxy implements Serializable{ 47private static final long serialVersionUID = 1; 48private final Date start; 49private final Date end; 50 51SerializationProxy(Period period) { 52this.start = period.start; 53this.end = period.end; 54} 55 56private Object readResolve() { 57return new Period(start, end); 58} 59} 60 61 } View Code
* 序列化代理模式的好處:
(1)可以阻止偽位元組流的攻擊。
(2)也可以阻止內部域的盜用攻擊。
(3)並且允許實際需要序列化的類的域宣告為final。
(4)序列化代理模式允許反序列化例項與原始序列化例項有著不同的類(如EnumSet的序列化代理)。
EnumSet序列化代理類程式碼:
1/** 2* This class is used to serialize all EnumSet instances, regardless of 3* implementation type.It captures their "logical contents" and they 4* are reconstructed using public static factories.This is necessary 5* to ensure that the existence of a particular implementation type is 6* an implementation detail. 7* 8* @serial include 9*/ 10private static class SerializationProxy <E extends Enum<E>> 11implements java.io.Serializable 12{ 13/** 14* The element type of this enum set. 15* 16* @serial 17*/ 18private final Class<E> elementType; 19 20/** 21* The elements contained in this enum set. 22* 23* @serial 24*/ 25private final Enum<?>[] elements; 26 27SerializationProxy(EnumSet<E> set) { 28elementType = set.elementType; 29elements = set.toArray(ZERO_LENGTH_ENUM_ARRAY); 30} 31 32// instead of cast to E, we should perhaps use elementType.cast() 33// to avoid injection of forged stream, but it will slow the implementation 34@SuppressWarnings("unchecked") 35private Object readResolve() { 36EnumSet<E> result = EnumSet.noneOf(elementType); 37for (Enum<?> e : elements) 38result.add((E)e); 39return result; 40} 41 42private static final long serialVersionUID = 362491234563181265L; 43} View Code
本文地址:ofollow,noindex">https://www.cnblogs.com/laishenghao/p/effective_java_note_serialization.html