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

Java 序列化與反序列化完整指南:原理、代碼實戰、serialVersionUID 與安全防範

     編輯:Java 相關知識

什麼是 Java 序列化?

序列化:將對象狀態轉換為字節序列(可存儲、傳輸)。
反序列化:從字節序列重建對象。
核心接口:java.io.Serializable(標記接口,無方法)。

核心應用場景

  • 持久化:對象存儲到本地文件/數據庫 BLOB
  • 網絡傳輸:RPC、分布式系統對象傳遞
  • 進程間通信:共享內存、消息隊列傳遞對象
  • 會話複製:Web 容器 Session 集群同步

完整代碼實戰

import java.io.*;

// 1. 實現 Serializable 接口
class Customer implements Serializable {
    private static final long serialVersionUID = 1L; // 強烈建議顯式聲明
    
    private String name;
    private int age;
    private transient String password; // transient 不參與序列化
    
    public Customer(String name, int age, String password) {
        this.name = name; this.age = age; this.password = password;
    }
    // getter/toString 省略
}

public class SerializationDemo {
    private static final String FILE = "customer.ser";

    public static void main(String[] args) {
        // === 序列化 ===
        Customer original = new Customer("張三", 25, "secret123");
        serialize(original);
        
        // === 反序列化 ===
        Customer restored = deserialize();
        System.out.println("原始: " + original);
        System.out.println("恢復: " + restored);
        // 輸出: password 為 null(被 transient 忽略)
    }

    static void serialize(Customer obj) {
        try (FileOutputStream fos = new FileOutputStream(FILE);
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            oos.writeObject(obj); // 核心方法:遞歸序列化對象圖
            System.out.println("序列化完成 -> " + FILE);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static Customer deserialize() {
        try (FileInputStream fis = new FileInputStream(FILE);
             ObjectInputStream ois = new ObjectInputStream(fis)) {
            return (Customer) ois.readObject(); // 核心方法:需強制轉型
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }
}

關鍵類與接口速查

類/接口角色核心方法
Serializable標記接口,聲明類可序列化無
ObjectOutputStream序列化輸出流writeObject(Object)
ObjectInputStream反序列化輸入流readObject()
Externalizable完全自定義序列化協議writeExternal/readExternal

serialVersionUID:版本控制核心

  • 作用:反序列化時驗證類版本一致性。不匹配拋 InvalidClassException。
  • 顯式聲明:private static final long serialVersionUID = 1L;(推薦)
  • 隱式生成:編譯器根據類結構計算,任何修改(增減字段、方法)都會導致值變化,極易報錯。
  • 最佳實踐:永遠顯式聲明,兼容性升級時保持不變;不兼容升級時主動修改。

進階特性與避坑指南

1. transient 關鍵字

修飾字段不參與序列化,反序列化時為默認值(null/0/false)。適用於敏感數據(密碼)、臨時緩存、非序列化對象引用。

2. 自定義序列化邏輯

private void writeObject(ObjectOutputStream out) throws IOException {
    out.defaultWriteObject(); // 默認序列化
    out.writeObject(encrypt(password)); // 手動寫入加密密碼
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject(); // 默認反序列化
    this.password = decrypt((String) in.readObject()); // 手動讀取解密
}

3. 靜態/瞬態字段不序列化

  • static 字段屬於類,不屬於實例,不序列化。
  • transient 字段顯式排除。

4. 父類未實現 Serializable

父類無 Serializable 但有無參構造器:反序列化時調用父類無參構造器初始化父類字段。
父類無無參構造器:拋 InvalidClassException。

安全風險與防範

⚠️ 反序列化漏洞(Gadget Chain):惡意構造字節流,利用 readObject/readResolve 等魔術方法執行任意代碼(如 Commons Collections、Spring、Fastjson 歷史漏洞)。

防護措施

  • 不反序列化不可信數據(首要原則)。
  • JDK 9+ 過濾器:ObjectInputFilter 白名單/黑名單類。
  • 避免使用 Java 原生序列化:改用 JSON (Jackson/Gson)、Protobuf、Avro 等安全、跨語言、高性能格式。
  • 實現 readObjectNoData() 處理超級類不匹配情況。

總結

Java 原生序列化雖然簡單,但性能一般、跨語言差、安全風險高。現代架構中:

  • 內部 RPC/存儲:首選 Protobuf / Avro / FlatBuffers。
  • Web API/配置:首選 JSON (Jackson)。
  • 不得已使用原生:顯式 serialVersionUID、transient 敏感字段、啟用 ObjectInputFilter、嚴格校驗來源。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved