## Chapter 12. Serialization(序列化)
### Item 90: Consider serialization proxies instead of serialized instances(考慮以序列化代理代替序列化實例)
As mentioned in Items 85 and 86 and discussed throughout this chapter, the decision to implement Serializable increases the likelihood of bugs and security problems as it allows instances to be created using an extralinguistic mechanism in place of ordinary constructors. There is, however, a technique that greatly reduces these risks. This technique is known as the serialization proxy pattern.
正如在 [Item-85](/Chapter-12/Chapter-12-Item-85-Prefer-alternatives-to-Java-serialization.md) 和 [Item-86](/Chapter-12/Chapter-12-Item-86-Implement-Serializable-with-great-caution.md) 中提到的貫穿本章的問題:實現 Serializable 接口的決定增加了出現 bug 和安全問題的可能性,因為它允許使用一種超語言機制來創建實例,而不是使用普通的構造函數。然而,有一種技術可以大大降低這些風險。這種技術稱為序列化代理模式。
The serialization proxy pattern is reasonably straightforward. First, design a private static nested class that concisely represents the logical state of an instance of the enclosing class. This nested class is known as the serialization proxy of the enclosing class. It should have a single constructor, whose parameter type is the enclosing class. This constructor merely copies the data from its argument: it need not do any consistency checking or defensive copying. By design, the default serialized form of the serialization proxy is the perfect serialized form of the enclosing class. Both the enclosing class and its serialization proxy must be declared to implement Serializable.
序列化代理模式相當簡單。首先,設計一個私有靜態嵌套類,它簡潔地表示外圍類實例的邏輯狀態。這個嵌套類稱為外圍類的序列化代理。它應該有一個構造函數,其參數類型是外圍類。這個構造函數只是從它的參數復制數據:它不需要做任何一致性檢查或防御性復制。按照設計,序列化代理的默認序列化形式是外圍類的完美序列化形式。外圍類及其序列代理都必須聲明實現 Serializable 接口。
For example, consider the immutable Period class written in Item 50 and made serializable in Item 88. Here is a serialization proxy for this class. Period is so simple that its serialization proxy has exactly the same fields as the class:
例如,考慮 [Item-50](/Chapter-8/Chapter-8-Item-50-Make-defensive-copies-when-needed.md) 中編寫的不可變 Period 類,并在 [Item-88](/Chapter-12/Chapter-12-Item-88-Write-readObject-methods-defensively.md) 中使其可序列化。這是該類的序列化代理。Period 非常簡單,它的序列化代理具有與類完全相同的字段:
```
// Serialization proxy for Period class
private static class SerializationProxy implements Serializable {
private final Date start;
private final Date end;
SerializationProxy(Period p) {
this.start = p.start;
this.end = p.end;
}
private static final long serialVersionUID =234098243823485285L; // Any number will do (Item 87)
}
```
Next, add the following writeReplace method to the enclosing class. This method can be copied verbatim into any class with a serialization proxy:
接下來,將以下 writeReplace 方法添加到外圍類中。通過序列化代理,這個方法可以被逐字地復制到任何類中:
```
// writeReplace method for the serialization proxy pattern
private Object writeReplace() {
return new SerializationProxy(this);
}
```
The presence of this method on the enclosing class causes the serialization system to emit a SerializationProxy instance instead of an instance of the enclosing class. In other words, the writeReplace method translates an instance of the enclosing class to its serialization proxy prior to serialization.
該方法存在于外圍類,導致序列化系統產生 SerializationProxy 實例,而不是外圍類的實例。換句話說,writeReplace 方法在序列化之前將外圍類的實例轉換為其序列化代理。
With this writeReplace method in place, the serialization system will never generate a serialized instance of the enclosing class, but an attacker might fabricate one in an attempt to violate the class’s invariants. To guarantee that such an attack would fail, merely add this readObject method to the enclosing class:
有了這個 writeReplace 方法,序列化系統將永遠不會生成外圍類的序列化實例,但是攻擊者可能會創建一個實例,試圖違反類的不變性。為了保證這樣的攻擊會失敗,只需將這個 readObject 方法添加到外圍類中:
```
// readObject method for the serialization proxy pattern
private void readObject(ObjectInputStream stream) throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
```
Finally, provide a readResolve method on the SerializationProxy class that returns a logically equivalent instance of the enclosing class. The presence of this method causes the serialization system to translate the serialization proxy back into an instance of the enclosing class upon deserialization.
最后,在 SerializationProxy 類上提供一個 readResolve 方法,該方法返回外圍類的邏輯等效實例。此方法的存在導致序列化系統在反序列化時將序列化代理轉換回外圍類的實例。
This readResolve method creates an instance of the enclosing class using only its public API and therein lies the beauty of the pattern. It largely eliminates the extralinguistic character of serialization, because the deserialized instance is created using the same constructors, static factories, and methods as any other instance. This frees you from having to separately ensure that deserialized instances obey the class’s invariants. If the class’s static factories or constructors establish these invariants and its instance methods maintain them, you’ve ensured that the invariants will be maintained by serialization as well.
這個 readResolve 方法僅使用其公共 API 創建了一個外圍類的實例,這就是該模式的美妙之處。它在很大程度上消除了序列化的語言外特性,因為反序列化實例是使用與任何其他實例相同的構造函數、靜態工廠和方法創建的。這使你不必單獨確保反序列化的實例遵從類的不變性。如果類的靜態工廠或構造函數建立了這些不變性,而它的實例方法維護它們,那么你就確保了這些不變性也將通過序列化來維護。
Here is the readResolve method for Period.SerializationProxy above:
以下是上述 `Period.SerializationProxy` 的 readResolve 方法:
```
// readResolve method for Period.SerializationProxy
private Object readResolve() {
return new Period(start, end); // Uses public constructor
}
```
Like the defensive copying approach (page 357), the serialization proxy approach stops the bogus byte-stream attack (page 354) and the internal field theft attack (page 356) dead in their tracks. Unlike the two previous approaches, this one allows the fields of Period to be final, which is required in order for the Period class to be truly immutable (Item 17). And unlike the two previous approaches, this one doesn’t involve a great deal of thought. You don’t have to figure out which fields might be compromised by devious serialization attacks, nor do you have to explicitly perform validity checking as part of deserialization.
與防御性復制方法(第 357 頁)類似,序列化代理方法阻止偽字節流攻擊(第 354 頁)和內部字段盜竊攻擊(第 356 頁)。與前兩種方法不同,這種方法允許 Period 的字段為 final,這是 Period 類真正不可變所必需的([Item-17](/Chapter-4/Chapter-4-Item-17-Minimize-mutability.md))。與前兩種方法不同,這一種方法不需要太多的思考。你不必指出哪些字段可能會受到狡猾的序列化攻擊的危害,也不必顯式地執行有效性檢查作為反序列化的一部分。
There is another way in which the serialization proxy pattern is more powerful than defensive copying in readObject. The serialization proxy pattern allows the deserialized instance to have a different class from the originally serialized instance. You might not think that this would be useful in practice, but it is.
序列化代理模式還有另一種方式比 readObject 中的防御性復制更強大。序列化代理模式允許反序列化實例具有與初始序列化實例不同的類。你可能不認為這在實踐中有用,但它確實有用。
Consider the case of EnumSet (Item 36). This class has no public constructors, only static factories. From the client’s perspective, they return EnumSet instances, but in the current OpenJDK implementation, they return one of two subclasses, depending on the size of the underlying enum type. If the underlying enum type has sixty-four or fewer elements, the static factories return a RegularEnumSet; otherwise, they return a JumboEnumSet.
考慮 EnumSet 的情況([Item-36](/Chapter-6/Chapter-6-Item-36-Use-EnumSet-instead-of-bit-fields.md))。該類沒有公共構造函數,只有靜態工廠。從客戶端的角度來看,它們返回 EnumSet 實例,但是在當前 OpenJDK 實現中,它們返回兩個子類中的一個,具體取決于底層枚舉類型的大小。如果底層枚舉類型有 64 個或更少的元素,則靜態工廠返回一個 RegularEnumSet;否則,它們返回一個 JumboEnumSet。
Now consider what happens if you serialize an enum set whose enum type has sixty elements, then add five more elements to the enum type, and then deserialize the enum set. It was a RegularEnumSet instance when it was serialized, but it had better be a JumboEnumSet instance once it is deserialized. In fact that’s exactly what happens, because EnumSet uses the serialization proxy pattern. In case you’re curious, here is EnumSet’s serialization proxy. It really is this simple:
現在考慮,如果序列化一個枚舉集合,它的枚舉類型有 60 個元素,然后給這個枚舉類型再增加 5 個元素,之后反序列化這個枚舉集合。當它被序列化的時候,返回 RegularEnumSet 實例,但最好是 JumboEnumSet 實例。事實上正是這樣,因為 EnumSet 使用序列化代理模式。如果你好奇,這里是 EnumSet 的序列化代理。其實很簡單:
```
// EnumSet's serialization proxy
private static class SerializationProxy <E extends Enum<E>> implements Serializable {
// The element type of this enum set.
private final Class<E> elementType;
// The elements contained in this enum set.
private final Enum<?>[] elements;
SerializationProxy(EnumSet<E> set) {
elementType = set.elementType;
elements = set.toArray(new Enum<?>[0]);
}
private Object readResolve() {
EnumSet<E> result = EnumSet.noneOf(elementType);
for (Enum<?> e : elements)
result.add((E)e);
return result;
}
private static final long serialVersionUID =362491234563181265L;
}
```
The serialization proxy pattern has two limitations. It is not compatible with classes that are extendable by their users (Item 19). Also, it is not compatible with some classes whose object graphs contain circularities: if you attempt to invoke a method on such an object from within its serialization proxy’s readResolve method, you’ll get a ClassCastException because you don’t have the object yet, only its serialization proxy.
序列化代理模式有兩個限制。它與客戶端可擴展的類不兼容([Item-19](/Chapter-4/Chapter-4-Item-19-Design-and-document-for-inheritance-or-else-prohibit-it.md))。而且,它也不能與對象圖中包含循環的某些類兼容:如果你試圖從對象的序列化代理的 readResolve 方法中調用對象上的方法,你將得到一個 ClassCastException,因為你還沒有對象,只有對象的序列化代理。
Finally, the added power and safety of the serialization proxy pattern are not free. On my machine, it is 14 percent more expensive to serialize and deserialize Period instances with serialization proxies than it is with defensive copying.
最后,序列化代理模式所增強的功能和安全性并不是沒有代價的。在我的機器上,通過序列化代理來序列化和反序列化 Period 實例的開銷,比用保護性拷貝進行的開銷增加了14%。
In summary, consider the serialization proxy pattern whenever you find yourself having to write a readObject or writeObject method on a class that is not extendable by its clients. This pattern is perhaps the easiest way to robustly serialize objects with nontrivial invariants.
總之,當你發現必須在客戶端不可擴展的類上編寫 readObject 或 writeObject 方法時,請考慮序列化代理模式。要想穩健地將帶有重要約束條件的對象序列化時,這種模式可能是最容易的方法。
---
**[Back to contents of the chapter(返回章節目錄)](/Chapter-12/Chapter-12-Introduction.md)**
- **Previous Item(上一條目):[Item 89: For instance control prefer enum types to readResolve(對于實例控制,枚舉類型優于 readResolve)](/Chapter-12/Chapter-12-Item-89-For-instance-control-prefer-enum-types-to-readResolve.md)**
- Chapter 2. Creating and Destroying Objects(創建和銷毀對象)
- Item 1: Consider static factory methods instead of constructors(考慮以靜態工廠方法代替構造函數)
- Item 2: Consider a builder when faced with many constructor parameters(在面對多個構造函數參數時,請考慮構建器)
- Item 3: Enforce the singleton property with a private constructor or an enum type(使用私有構造函數或枚舉類型實施單例屬性)
- Item 4: Enforce noninstantiability with a private constructor(用私有構造函數實施不可實例化)
- Item 5: Prefer dependency injection to hardwiring resources(依賴注入優于硬連接資源)
- Item 6: Avoid creating unnecessary objects(避免創建不必要的對象)
- Item 7: Eliminate obsolete object references(排除過時的對象引用)
- Item 8: Avoid finalizers and cleaners(避免使用終結器和清除器)
- Item 9: Prefer try with resources to try finally(使用 try-with-resources 優于 try-finally)
- Chapter 3. Methods Common to All Objects(對象的通用方法)
- Item 10: Obey the general contract when overriding equals(覆蓋 equals 方法時應遵守的約定)
- Item 11: Always override hashCode when you override equals(當覆蓋 equals 方法時,總要覆蓋 hashCode 方法)
- Item 12: Always override toString(始終覆蓋 toString 方法)
- Item 13: Override clone judiciously(明智地覆蓋 clone 方法)
- Item 14: Consider implementing Comparable(考慮實現 Comparable 接口)
- Chapter 4. Classes and Interfaces(類和接口)
- Item 15: Minimize the accessibility of classes and members(盡量減少類和成員的可訪問性)
- Item 16: In public classes use accessor methods not public fields(在公共類中,使用訪問器方法,而不是公共字段)
- Item 17: Minimize mutability(減少可變性)
- Item 18: Favor composition over inheritance(優先選擇復合而不是繼承)
- Item 19: Design and document for inheritance or else prohibit it(繼承要設計良好并且具有文檔,否則禁止使用)
- Item 20: Prefer interfaces to abstract classes(接口優于抽象類)
- Item 21: Design interfaces for posterity(為后代設計接口)
- Item 22: Use interfaces only to define types(接口只用于定義類型)
- Item 23: Prefer class hierarchies to tagged classes(類層次結構優于帶標簽的類)
- Item 24: Favor static member classes over nonstatic(靜態成員類優于非靜態成員類)
- Item 25: Limit source files to a single top level class(源文件僅限有單個頂層類)
- Chapter 5. Generics(泛型)
- Item 26: Do not use raw types(不要使用原始類型)
- Item 27: Eliminate unchecked warnings(消除 unchecked 警告)
- Item 28: Prefer lists to arrays(list 優于數組)
- Item 29: Favor generic types(優先使用泛型)
- Item 30: Favor generic methods(優先使用泛型方法)
- Item 31: Use bounded wildcards to increase API flexibility(使用有界通配符增加 API 的靈活性)
- Item 32: Combine generics and varargs judiciously(明智地合用泛型和可變參數)
- Item 33: Consider typesafe heterogeneous containers(考慮類型安全的異構容器)
- Chapter 6. Enums and Annotations(枚舉和注解)
- Item 34: Use enums instead of int constants(用枚舉類型代替 int 常量)
- Item 35: Use instance fields instead of ordinals(使用實例字段替代序數)
- Item 36: Use EnumSet instead of bit fields(用 EnumSet 替代位字段)
- Item 37: Use EnumMap instead of ordinal indexing(使用 EnumMap 替換序數索引)
- Item 38: Emulate extensible enums with interfaces(使用接口模擬可擴展枚舉)
- Item 39: Prefer annotations to naming patterns(注解優于命名模式)
- Item 40: Consistently use the Override annotation(堅持使用 @Override 注解)
- Item 41: Use marker interfaces to define types(使用標記接口定義類型)
- Chapter 7. Lambdas and Streams(λ 表達式和流)
- Item 42: Prefer lambdas to anonymous classes(λ 表達式優于匿名類)
- Item 43: Prefer method references to lambdas(方法引用優于 λ 表達式)
- Item 44: Favor the use of standard functional interfaces(優先使用標準函數式接口)
- Item 45: Use streams judiciously(明智地使用流)
- Item 46: Prefer side effect free functions in streams(在流中使用無副作用的函數)
- Item 47: Prefer Collection to Stream as a return type(優先選擇 Collection 而不是流作為返回類型)
- Item 48: Use caution when making streams parallel(謹慎使用并行流)
- Chapter 8. Methods(方法)
- Item 49: Check parameters for validity(檢查參數的有效性)
- Item 50: Make defensive copies when needed(在需要時制作防御性副本)
- Item 51: Design method signatures carefully(仔細設計方法簽名)
- Item 52: Use overloading judiciously(明智地使用重載)
- Item 53: Use varargs judiciously(明智地使用可變參數)
- Item 54: Return empty collections or arrays, not nulls(返回空集合或數組,而不是 null)
- Item 55: Return optionals judiciously(明智地的返回 Optional)
- Item 56: Write doc comments for all exposed API elements(為所有公開的 API 元素編寫文檔注釋)
- Chapter 9. General Programming(通用程序設計)
- Item 57: Minimize the scope of local variables(將局部變量的作用域最小化)
- Item 58: Prefer for-each loops to traditional for loops(for-each 循環優于傳統的 for 循環)
- Item 59: Know and use the libraries(了解并使用庫)
- Item 60: Avoid float and double if exact answers are required(若需要精確答案就應避免使用 float 和 double 類型)
- Item 61: Prefer primitive types to boxed primitives(基本數據類型優于包裝類)
- Item 62: Avoid strings where other types are more appropriate(其他類型更合適時應避免使用字符串)
- Item 63: Beware the performance of string concatenation(當心字符串連接引起的性能問題)
- Item 64: Refer to objects by their interfaces(通過接口引用對象)
- Item 65: Prefer interfaces to reflection(接口優于反射)
- Item 66: Use native methods judiciously(明智地使用本地方法)
- Item 67: Optimize judiciously(明智地進行優化)
- Item 68: Adhere to generally accepted naming conventions(遵守被廣泛認可的命名約定)
- Chapter 10. Exceptions(異常)
- Item 69: Use exceptions only for exceptional conditions(僅在確有異常條件下使用異常)
- Item 70: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors(對可恢復情況使用 checked 異常,對編程錯誤使用運行時異常)
- Item 71: Avoid unnecessary use of checked exceptions(避免不必要地使用 checked 異常)
- Item 72: Favor the use of standard exceptions(鼓勵復用標準異常)
- Item 73: Throw exceptions appropriate to the abstraction(拋出能用抽象解釋的異常)
- Item 74: Document all exceptions thrown by each method(為每個方法記錄會拋出的所有異常)
- Item 75: Include failure capture information in detail messages(異常詳細消息中應包含捕獲失敗的信息)
- Item 76: Strive for failure atomicity(盡力保證故障原子性)
- Item 77: Don’t ignore exceptions(不要忽略異常)
- Chapter 11. Concurrency(并發)
- Item 78: Synchronize access to shared mutable data(對共享可變數據的同步訪問)
- Item 79: Avoid excessive synchronization(避免過度同步)
- Item 80: Prefer executors, tasks, and streams to threads(Executor、task、流優于直接使用線程)
- Item 81: Prefer concurrency utilities to wait and notify(并發實用工具優于 wait 和 notify)
- Item 82: Document thread safety(文檔應包含線程安全屬性)
- Item 83: Use lazy initialization judiciously(明智地使用延遲初始化)
- Item 84: Don’t depend on the thread scheduler(不要依賴線程調度器)
- Chapter 12. Serialization(序列化)
- Item 85: Prefer alternatives to Java serialization(優先選擇 Java 序列化的替代方案)
- Item 86: Implement Serializable with great caution(非常謹慎地實現 Serializable)
- Item 87: Consider using a custom serialized form(考慮使用自定義序列化形式)
- Item 88: Write readObject methods defensively(防御性地編寫 readObject 方法)
- Item 89: For instance control, prefer enum types to readResolve(對于實例控制,枚舉類型優于 readResolve)
- Item 90: Consider serialization proxies instead of serialized instances(考慮以序列化代理代替序列化實例)