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 以註解為基礎,藉由在類別與其欄位上標註,來控制序列化/反序列化的過程。
主要註解
| 註解 | 用途 |
|---|---|
|
指定 XML 的根元素(即類別本身) |
|
將欄位/屬性標註為 XML 元素 |
|
將欄位/屬性標註為 XML 屬性 |
|
控制元素順序、型別名稱等 |
|
將欄位排除在序列化之外 |
主要類別
- 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 中的效果 |
|---|---|---|
|
類別 | 根元素 |
|
getter/欄位 | XML 內部元素 |
|
getter/欄位 | 元素的屬性 |
|
集合的 getter | 集合的「包裝」元素(例如 <list>) |
|
欄位/getter | 將欄位排除於序列化之外 |
|
類別 | 控制元素順序與型別名稱 |
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,或將值序列化為字串。
GO TO FULL VERSION