首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 序列化機制全解析:原理、實戰與 transient 關鍵字安全防禦指南

     編輯:Java 綜合教學

Java 序列化深度解析:對象持久化、網絡傳輸與安全防禦實戰

Java 序列化 是將對象狀態轉換為位元組流以便持久化存儲或網絡傳輸的核心機制。本文將系統講解序列化概念目的、Serializable 介面實現流程、ObjectOutputStream/InputStream 核心 API、transient 關鍵字與安全防禦、serialVersionUID 版本控制、自定義序列化策略、以及主流替代方案對比,構建完整知識體系。

1. 核心概念與目的

  • 定義:將對象圖 (對象及其引用鏈) 轉為位元組流的過程叫序列化;逆過程叫反序列化
  • 目的:對象持久化 (文件/數據庫)、進程間通訊 (RMI/Socket)、分散式會話共享、緩存存儲
  • 圖結構:對象引用關係形成有向圖,序列化會遞歸遍歷完整圖

2. 實現序列化:三步走流程

步驟 1:實現 Serializable 標記介面

public class Student implements Serializable {
    private static final long serialVersionUID = 1L; // 強烈建議顯式聲明
    private int id;
    private String name;
    private int age;
    private String department;
    // 構造器、getter/setter、toString...
}

Serializable 無任何方法,僅作標記,告訴 JVM「該類允許序列化」。

步驟 2:序列化寫入 (ObjectOutputStream)

Student stu = new Student(981036, "chb", 23, "CSD");
try (FileOutputStream fos = new FileOutputStream("data.ser");
     ObjectOutputStream oos = new ObjectOutputStream(fos)) {
    oos.writeObject(stu); // 核心方法:遞歸寫入對象圖
}

步驟 3:反序列化讀取 (ObjectInputStream)

try (FileInputStream fis = new FileInputStream("data.ser");
     ObjectInputStream ois = new ObjectInputStream(fis)) {
    Student restored = (Student) ois.readObject(); // 還原對象圖
    System.out.println(restored);
}

3. 能序列化什麼?不能序列化什麼?

類別示例說明
非 static、非 transient 實例字段id, name, age核心數據,自動序列化
static 靜態字段static int count屬於類,不屬於對象,**不序列化**
transient 字段transient String pwd**顯式排除**,反序列化為默認值 (null/0/false)
方法、構造器getName()不屬於對象狀態,**不序列化**
引用對象Address address若 Address 也實現 Serializable,遞歸序列化

4. transient 關鍵字:雙重防禦作用

場景一:非可序列化對象引用

Thread、FileInputStream、Socket、Connection 等持有系統資源的對象無法序列化,必須標記 transient,否則拋 NotSerializableException。

場景二:敏感數據安全防禦

密碼、Token、銀行卡號等敏感字段 禁止直接序列化到磁盤/網絡。標記 transient 後,反序列化為 null,業務層重新加密賦值。

5. serialVersionUID:版本控制生命線

  • JVM 根據類結構自動生成,結構變更 (增減字段/方法) 導致 UID 變化 → 反序列化拋 InvalidClassException
  • 強烈建議顯式聲明:private static final long serialVersionUID = 1L;
  • 兼容性規則:只增字段兼容,減字段/改類型/改修飾符不兼容

6. 進階:自定義序列化策略

私有方法對控制序列化行為

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()); // 讀取解密
}

Externalizable 介面:完全控制

public class Data implements Externalizable {
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(id);
        out.writeUTF(name);
    }
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.id = in.readInt();
        this.name = in.readUTF();
    }
}

需實現 writeExternal/readExternal,不再自動序列化任何字段,靈活度最高但編碼量大。

7. 安全漏洞與防禦:反序列化攻擊

  • 風險:惡意構造位元組流利用 Gadget 鏈 (Apache Commons Collections、Spring 等) 觸發 RCE
  • 防禦:
    • JDK 9+ ObjectInputFilter 白名單過濾
    • 不反序列化不可信數據源
    • 使用 JSON (Jackson/Gson)、Protobuf、Avro 替代 Java 原生序列化
    • 升級依賴庫修復已知 Gadget

8. 主流替代方案對比

方案優點缺點適用場景
Java 原生序列化零配置、支援對象圖、RMI 必需體積大、速度慢、不跨語言、安全風險高內部 RPC、舊系統兼容
JSON (Jackson/Gson)可讀、跨語言、生態成熟、安全無類型信息、對象圖需配置、循環引用需處理Web API、配置文件、日誌
Protobuf / Avro體積極小、速度極快、Schema 演進、跨語言需定義 .proto/.avsc、學習成本、不可直接讀高性能微服務、大數據傳輸、Kafka
Hessian / Kryo二進制、較快、支援對象圖、Java 生態跨語言弱、Kryo 需註冊類Dubbo 默認、緩存存儲

總結

Java 序列化雖然內建便捷,但體積大、速度慢、跨語言差、安全風險高。現代架構中:對外介面用 JSON/Protobuf,內部高性能用 Protobuf/Kryo,僅在 RMI/舊系統兼容時使用原生序列化。若必須用原生,請務必:serialVersionUID 顯式聲明、敏感字段 transient、啟用 ObjectInputFilter、定期掃描依賴漏洞。下一篇我們將深入 JVM 類加載機制與雙親委派模型。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved