對象序列化是將 Java 對象轉為字節序列(持久化到磁盤或網絡傳輸),反序列化是逆過程。本文系統講解 JDK 原生序列化 API、Serializable 與 Externalizable 兩大接口機制、自定義序列化策略、serialVersionUID 版本控制、安全風險與最佳實踐。
| 類/接口 | 包 | 核心方法 | 作用 |
|---|---|---|---|
ObjectOutputStream | java.io | writeObject(Object) | 對象 → 字節流(序列化) |
ObjectInputStream | java.io | readObject() | 字節流 → 對象(反序列化) |
public class SerializationDemo {
public static void main(String[] args) throws Exception {
// 序列化
try (ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("user.ser"))) {
User user = new User("Alice", 28, "secret123");
out.writeObject(user);
out.writeObject("附加字符串");
out.writeInt(2024);
}
// 反序列化(順序必須一致)
try (ObjectInputStream in = new ObjectInputStream(
new FileInputStream("user.ser"))) {
User u = (User) in.readObject();
String s = (String) in.readObject();
int year = in.readInt();
System.out.println(u); // User{name='Alice', age=28, password='null'}
System.out.println(s); // 附加字符串
System.out.println(year); // 2024
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 1L; // 強烈建議顯式聲明
private String name;
private int age;
private transient String password; // transient 不參與序列化
public User(String name, int age, String password) {
this.name = name; this.age = age; this.password = password;
}
@Override public String toString() {
return "User{name='" + name + "', age=" + age + ", password='" + password + "'}";
}
}
writeObject / readObject在可序列化類中定義 private 方法,JVM 會優先調用:
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 先執行默認序列化
// 手動處理 transient 敏感字段:加密後寫入
out.writeObject(encrypt(password));
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 先執行默認反序列化
// 手動讀取並解密
this.password = decrypt((String) in.readObject());
}
場景:敏感字段加密、衍生字段重算、補充上下文信息。
transient:跳過序列化,節省空間與時間Externalizable extends Serializable,強制實現兩個 public 方法:
public class UserExt implements Externalizable {
private String name; private int age;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
// 完全自定義格式、壓縮、加密
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.name = (String) in.readObject();
this.age = in.readInt();
}
// 關鍵:反序列化時會先調用 無參構造器
public UserExt() {} // 必須提供 public 無參構造器!
}
差異點:
public no-arg constructor(否則拋 InvalidClassException)反序列化時,JVM 比較流中的 serialVersionUID 與當前類的 serialVersionUID:
InvalidClassException// 兼容升級示例
private static final long serialVersionUID = 1L; // 保持不變
private String newField = "default"; // 新增字段,反序列化舊數據時自動賦默認值
惡意構造的字節流在反序列化過程中觸發 readObject、readResolve 等鏈,導致 RCE(遠程代碼執行)。著名庫:Apache Commons Collections、Spring、Fastjson 等均有歷史漏洞。
readObject()ObjectInputFilter (JDK 9+) 白名單過濾允許反序列化的類Serializable 而非 Externalizable(除非極致性能/特殊格式)serialVersionUIDtransient + 自定義 writeObject/readObject 加密transientObjectInputFilter 白名單