CodeGym /課程 /JAVA 25 SELF /透過 JAXB 操作 XML:基礎與註解

透過 JAXB 操作 XML:基礎與註解

JAVA 25 SELF
等級 47 , 課堂 3
開放

1. 認識 JAXB

JAXB(Java Architecture for XML Binding)是一種 Java 標準技術,用於將 Java 物件與 XML 相互轉換(binding)。使用 JAXB 可以輕鬆將物件序列化為 XML 檔案,並從這些檔案還原物件。

JAXB 在 Java 的標準程式庫中一直包含到 11 版為止。自 Java 11 起,JAXB 被移至獨立模組,需要透過 Maven/Gradle 引入或手動下載。對於現代版本的 Java,請加入以下相依:

<!-- Maven 範例 -->
<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.0</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>4.0.3</version>
</dependency>

為什麼需要 XML?

  • XML 是通用、可讀性高的格式,廣泛用於系統間的資料交換、設定檔與資訊儲存。
  • 與二進位序列化不同,XML 容易以肉眼閱讀、可依 schema 驗證,亦可在瀏覽器中開啟。

2. JAXB 的主要類別與註解

JAXB 以註解為基礎,藉由在類別與其欄位上標註,來控制序列化/反序列化的過程。

主要註解

註解 用途
@XmlRootElement
指定 XML 的根元素(即類別本身)
@XmlElement
將欄位/屬性標註為 XML 元素
@XmlAttribute
將欄位/屬性標註為 XML 屬性
@XmlType
控制元素順序、型別名稱等
@XmlTransient
將欄位排除在序列化之外

主要類別

  • JAXBContext — 入口點,為特定類別建立(反)序列化的 context。
  • Marshaller — 將物件轉為 XML(marshalling,marshal())。
  • Unmarshaller — 將 XML 轉為物件(unmarshalling,unmarshal())。

3. 範例:將物件序列化為 XML

先建立一個要序列化的類別。假設這是我們遊戲中的角色:

import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlAttribute;

@XmlRootElement(name = "player")
public class Player {
    private String name;
    private int level;
    private int health;

    public Player() {} // 必須的無參數建構子!

    public Player(String name, int level, int health) {
        this.name = name;
        this.level = level;
        this.health = health;
    }

    @XmlElement
    public String getName() {
        return name;
    }

    public void setName(String name) { this.name = name; }

    @XmlElement
    public int getLevel() {
        return level;
    }

    public void setLevel(int level) { this.level = level; }

    @XmlAttribute
    public int getHealth() {
        return health;
    }

    public void setHealth(int health) { this.health = health; }
}
  • @XmlRootElement(name = "player") — 類別會對應為根元素 <player>
  • @XmlElement — 欄位會成為獨立的 XML 元素(<name><level>)。
  • @XmlAttribute — 欄位會成為根元素的屬性(health="100")。
  • 別忘了無參數建構子!JAXB 在反序列化時需要它。

將物件序列化為 XML

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Marshaller;

public class Main {
    public static void main(String[] args) throws Exception {
        Player player = new Player("Aragorn", 5, 100);

        JAXBContext context = JAXBContext.newInstance(Player.class);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); // 格式化輸出

        marshaller.marshal(player, System.out); // 將 XML 輸出到主控台
        // marshaller.marshal(player, new File("player.xml")); // 或寫入檔案
    }
}

結果:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<player health="100">
    <name>Aragorn</name>
    <level>5</level>
</player>

從 XML 反序列化物件

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.Unmarshaller;
import java.io.File;

public class Main {
    public static void main(String[] args) throws Exception {
        JAXBContext context = JAXBContext.newInstance(Player.class);
        Unmarshaller unmarshaller = context.createUnmarshaller();

        Player player = (Player) unmarshaller.unmarshal(new File("player.xml"));
        System.out.println(player.getName() + ", 等級: " + player.getLevel() + ", 生命值: " + player.getHealth());
    }
}

4. JAXB 的特性與限制

對類別的要求

  • public 無參數建構子 — 必須具備。
  • 為了正確運作,請使用 getter 與 setter。
  • 所有可序列化的欄位應可存取(透過 public API)。
  • 巢狀物件與集合也必須可序列化(加上註解並提供無參數建構子)。

處理集合與巢狀物件

假設玩家有一個物品欄(清單)。如何序列化集合?

import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;

@XmlRootElement(name = "player")
public class Player {
    // ... 其餘欄位

    private List<String> inventory;

    public Player() {}

    // ... 其餘 getter/setter

    @XmlElementWrapper(name = "inventory")
    @XmlElement(name = "item")
    public List<String> getInventory() {
        return inventory;
    }

    public void setInventory(List<String> inventory) {
        this.inventory = inventory;
    }
}

序列化結果:

<player health="100">
    <name>Aragorn</name>
    <level>5</level>
    <inventory>
        <item>Sword</item>
        <item>Shield</item>
    </inventory>
</player>
  • @XmlElementWrapper — 為集合建立「包裝」元素(元素 <inventory>)。
  • @XmlElement(name = "item") — 清單中的每個元素將序列化為 <item>

如果有巢狀物件(例如 Position),也要為其加上註解並提供無參數建構子。

5. 實作:將物件序列化與反序列化為 XML

import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlAttribute;
import java.util.List;

@XmlRootElement(name = "player")
public class Player {
    private String name;
    private int level;
    private int health;
    private List<String> inventory;
    private Position position;

    public Player() {}

    public Player(String name, int level, int health, List<String> inventory, Position position) {
        this.name = name;
        this.level = level;
        this.health = health;
        this.inventory = inventory;
        this.position = position;
    }

    @XmlElement
    public String getName() { return name; }

    @XmlElement
    public int getLevel() { return level; }

    @XmlAttribute
    public int getHealth() { return health; }

    @XmlElementWrapper(name = "inventory")
    @XmlElement(name = "item")
    public List<String> getInventory() { return inventory; }

    @XmlElement
    public Position getPosition() { return position; }

    // 為簡潔起見,省略 setter
}

@XmlRootElement(name = "position")
class Position {
    private int x;
    private int y;

    public Position() {}

    public Position(int x, int y) { this.x = x; this.y = y; }

    @XmlAttribute
    public int getX() { return x; }

    @XmlAttribute
    public int getY() { return y; }

    // 省略 setter
}

序列化:

Player player = new Player(
    "Aragorn",
    5,
    100,
    List.of("Sword", "Shield", "Potion"),
    new Position(10, 20)
);

JAXBContext context = JAXBContext.newInstance(Player.class);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(player, System.out);

XML 結果:

<player health="100">
    <name>Aragorn</name>
    <level>5</level>
    <inventory>
        <item>Sword</item>
        <item>Shield</item>
        <item>Potion</item>
    </inventory>
    <position x="10" y="20"/>
</player>

反序列化 的方式相同:只要類別描述正確,JAXB 會自動處理巢狀物件與集合。

6. 表格:JAXB 主要註解與其效果

註解 用在何處 在 XML 中的效果
@XmlRootElement
類別 根元素
@XmlElement
getter/欄位 XML 內部元素
@XmlAttribute
getter/欄位 元素的屬性
@XmlElementWrapper
集合的 getter 集合的「包裝」元素(例如 <list>)
@XmlTransient
欄位/getter 將欄位排除於序列化之外
@XmlType
類別 控制元素順序與型別名稱

7. JAXB 的特性與限制

元素順序

預設情況下,JAXB 可能按字母順序輸出元素。若要明確指定順序,請使用 @XmlType 與屬性 propOrder

@XmlType(propOrder = {"name", "level", "inventory", "position"})

排除欄位

若不希望序列化某個欄位/getter,請使用 @XmlTransient

@XmlTransient
public String getSecretCode() { ... }

集合相關問題

  • 不要使用未加上泛型的原始集合類型:請寫 List<Type>,而不是 List
  • 如果集合存放的是物件,這些類別也必須加上註解並具有無參數建構子。

錯誤

  • 缺少無參數建構子 — 在反序列化時會拋出 JAXBException
  • 未標註的巢狀類別 — JAXB 無法序列化/反序列化它。
  • 非標準型別(例如 LocalDate)需要配接器(@XmlJavaTypeAdapter)。

8. 使用 JAXB 時的常見錯誤

錯誤 №1:缺少無參數建構子。JAXB 要求可序列化的類別必須有 public 無參數建構子。若沒有,反序列化(unmarshalling)時會拋出 JAXBException

錯誤 №2:未標註的巢狀物件。如果有物件型別的欄位,但其類別未標註 @XmlRootElement 或至少 @XmlType,JAXB 將無法正確地序列化/反序列化它。

錯誤 №3:集合處理問題。JAXB 無法理解未指定元素型別的原始集合。請使用泛型並正確標註集合(@XmlElementWrapper + @XmlElement)。

錯誤 №4:未明確控制元素順序。如果 XML 中的元素順序與整合需求相關,請使用帶有 propOrder@XmlType;否則 JAXB 可能會以其他順序輸出(例如依字母排序)。

錯誤 №5:未為非標準型別使用配接器。不某些型別(例如 LocalDate),JAXB 無法在沒有配接器的情況下序列化。請使用 @XmlJavaTypeAdapter,或將值序列化為字串。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION