1. JAXB 入门
JAXB(Java Architecture for XML Binding)是 Java 的标准技术,用于将(binding)Java 对象与 XML 相互转换。借助 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 — 入口点,为特定类创建序列化/反序列化上下文。
- Marshaller — 将对象转换为 XML(编组,marshal())。
- Unmarshaller — 将 XML 转换为对象(解组,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 的特性与限制
对类的要求
- 公共无参构造函数 — 必须提供。
- 为保证正常工作,请使用 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 要求可序列化的类必须有公共无参构造函数。如果没有,在解组时会抛出 JAXBException 异常。
错误 2:未加注解的嵌套对象。 如果某个字段是对象,但其类没有标注 @XmlRootElement 或至少 @XmlType,JAXB 将无法正确序列化/反序列化它。
错误 3:集合相关问题。 JAXB 无法理解未指定元素类型的“原始”集合。请使用泛型并正确标注集合(@XmlElementWrapper + @XmlElement)。
错误 4:未显式控制元素顺序。 如果 XML 中的元素顺序对集成很重要,请使用 @XmlType 搭配 propOrder;否则 JAXB 可能按其他顺序(例如字母顺序)输出元素。
错误 5:未为非标准类型提供适配器。 对于一些类型(例如 LocalDate),JAXB 不能直接序列化。请使用 @XmlJavaTypeAdapter,或将值序列化为字符串。
GO TO FULL VERSION