如何處理和修復 java.io.NotSerializableException
1. 引言
Java 序列化廣泛用於持久化物件狀態、在 JVM 之間傳輸資料以及支援分散式應用程式特性。然而,當物件圖包含不符合序列化規則的元素時,經常會發生序列化失敗。其中,當序列化遇到未實作Serializable介面的類別時,就會拋出java.io.NotSerializableException 例外。在大多數情況下,根本原因在於嵌套物件或字段,而不是主物件本身。這種現像在基於框架或函式庫的應用程式中更為明顯,因為這些應用程式中的物件關係複雜且通常是隱式的。
本教學將詳細介紹如何瞭解和管理 Java 應用程式中的NotSerializableException 。此外,我們還將重點介紹處理可序列化物件和不可序列化物件時需要考慮的關鍵因素,並深入探討在複雜應用程式環境中確保可靠序列化的最佳實踐。
2. 由不可序列化類別引起的序列化失敗
NotSerializableException的主要原因是類別沒有實作Serializable接口,而 Java 執行時間需要該介面才能將物件轉換為位元組流。
讓我們用一個例子來說明這種情況:
public class User {
private String name;
private int id;
private Address address;
public User(String name, int id, Address address) {
this.name = name;
this.id = id;
this.address = address;
}
}
在這種情況下,執行階段會為User類別引發java.io.NotSerializableException異常,因為它缺少序列化功能。
實作Serializable介面會使該類別符合 Java 執行時期序列化的條件:
public class User implements Serializable {
private String name;
private int id;
private Address address;
}
現階段,主要的序列化問題似乎已解決。但是,由於存在嵌套對象,序列化仍然可能失敗。
3. 由不可序列化的巢狀物件引起的序列化失敗
即使將User類別設為可序列化,如果其任何嵌套物件或欄位未實現Serializable接口,序列化仍然可能失敗。
3.1. 範例問題程式碼
讓我們以User類別為例來看一下,但這次包含一個嵌套的Address對象,該物件沒有實作Serializable介面:
class User implements Serializable {
private String name;
private int age;
private Address address; // Address does not implement Serializable
}
class Address {
String city;
String country;
}
在這種情況下,序列化失敗的原因是Address物件嵌套。 Java運行時會遍歷整個物件圖,因此可序列化類別所引用的每個物件也必須是可序列化的。由於Address不具備此功能,運行時會拋出java.io.NotSerializableException異常,導致序列化過程失敗。
處理不可序列化的巢狀物件主要有兩種方法。
3.2. 使巢狀類別可序列化(方案 1)
這種方法需要在嵌套的Address類別中實作Serializable :
public class Address implements Serializable {
private String city;
private String country;
}
透過這樣的改變,整個物件圖都變得可序列化,從而使運行時能夠成功序列化User 。此過程確保了物件狀態的準確維護和重構。
3.3. 將欄位宣告為瞬態(方案 2)
當Address物件不代表持久化狀態,而僅在運行時需要時,可以透過將其標記為 transient 來將其從序列化中排除:
public class User implements Serializable {
private String name;
private int age;
private transient Address address;
}
在這種情況下,運行時會從序列化形式中省略該字段,並且在反序列化期間不會恢復其值。
4. 由外部依賴項引起的序列化失敗
應用程式經常依賴外部依賴項來提供支援功能。這些外部相依性通常會引入執行時期管理的對象,而這些對象並未實作Serializable介面。當可序列化物件圖包含此類第三方物件時,Java 執行時間無法序列化它們,從而導致序列化失敗。
4.1. 範例問題程式碼
讓我們來看一段程式碼,它演示了一個模擬的AuditContext類,該類源自第三方庫,用於儲存與請求相關的元資料:
public class AuditContext {
private String traceId;
public AuditContext(String traceId) {
this.traceId = traceId;
}
public String getTraceId() {
return traceId;
}
}
由於第三方類別沒有實作Serializable 接口,因此處理它需要兩種常見的方法:
4.2. 將欄位宣告為瞬態(方案 1)
在第一種方法中,第三方物件被排除在序列化之外,而其他User資料保持不變:
private transient AuditContext auditContext;
我們透過將場標記為瞬態來實現這一點。
4.3. 擷取並儲存可序列化部分(方案 2)
另一種方法是只儲存參與序列化的物件部分。
在這種情況下, traceId表示來自AuditContext可序列化資料:
public class User implements Serializable {
private String name;
private int age;
private Address address;
private String traceId; // extracted from third-party object
public User(String name, int age, Address address, AuditContext auditContext) {
this.name = name;
this.age = age;
this.address = address;
this.traceId = auditContext.getTraceId(); // extract serializable data
}
兩種方法都能成功序列化User對象,同時保持預期的應用程式資料或執行時間上下文。
5. 結論
本文探討了在Java應用程式中處理和解決java.io.NotSerializableException的有效策略,並專注於物件圖中的問題。分析表明,解決此異常不僅僅是將單一類別標記為Serializable ,還需要考慮參與序列化的巢狀欄位和第三方物件。為了使根物件能夠成功序列化,所有嵌套物件和欄位本身都必須是可序列化的。
所提出的解決方案包括:在需要持久化時使類別Serializable ;在需要排除某些狀態時將欄位標記為瞬態;以及僅從外部依賴項中提取可序列化資料。這些方法支援穩定的序列化,使開發人員能夠清晰地控制物件狀態和應用程式結構。總而言之,這些技術有助於維護可靠的序列化和健全的應用程式設計。
和往常一樣,原始碼可以在 GitHub 上找到。