CodeGym /课程 /JAVA 25 SELF /通过 JAXB 处理 XML:基础与注解

通过 JAXB 处理 XML:基础与注解

JAVA 25 SELF
第 47 级 , 课程 3
可用

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 基于注解工作,通过在类及其字段/属性上标注注解来控制序列化/反序列化过程。

核心注解

注解 用途
@XmlRootElement
标记 XML 的根元素(类本身)
@XmlElement
将字段/属性标记为 XML 元素
@XmlAttribute
将字段/属性标记为 XML 属性
@XmlType
控制元素顺序、类型名等
@XmlTransient
从序列化中排除字段

核心类

  • 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 中的作用
@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 要求可序列化的类必须有公共无参构造函数。如果没有,在解组时会抛出 JAXBException 异常。

错误 2:未加注解的嵌套对象。 如果某个字段是对象,但其类没有标注 @XmlRootElement 或至少 @XmlType,JAXB 将无法正确序列化/反序列化它。

错误 3:集合相关问题。 JAXB 无法理解未指定元素类型的“原始”集合。请使用泛型并正确标注集合(@XmlElementWrapper + @XmlElement)。

错误 4:未显式控制元素顺序。 如果 XML 中的元素顺序对集成很重要,请使用 @XmlType 搭配 propOrder;否则 JAXB 可能按其他顺序(例如字母顺序)输出元素。

错误 5:未为非标准类型提供适配器。 对于一些类型(例如 LocalDate),JAXB 不能直接序列化。请使用 @XmlJavaTypeAdapter,或将值序列化为字符串。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION