杰瑞科技汇

Java中XML序列化与反序列化如何实现?

XML 序列化是指将 Java 对象转换为 XML 格式的字符串或文件的过程,反序列化则相反,是将 XML 格式的数据重新转换回 Java 对象的过程。

Java中XML序列化与反序列化如何实现?-图1
(图片来源网络,侵删)

在 Java 中,主要有两种方式来实现 XML 序列化/反序列化:

  1. JAXB (Java Architecture for XML Binding):这是 Java 标准库(从 Java 6 开始内置)提供的方式,是现在推荐的做法,它通过注解将 Java 类和 XML 元素/属性绑定起来,非常方便。
  2. 传统方式(使用 javax.xml 包):这种方式非常繁琐,需要手动创建 Document 对象、遍历节点等,代码量巨大,现在基本不再使用。

我们将重点介绍 JAXB,因为它更现代、更高效。


使用 JAXB (推荐方法)

JAXB 的核心思想是通过注解来定义 Java 对象与 XML 之间的映射关系。

核心注解

  • @XmlRootElement: 将一个类映射到 XML 的根元素。name 属性指定 XML 元素的名称。
  • @XmlElement: 将一个字段或属性映射到 XML 的一个元素。name 属性指定 XML 元素的名称。
  • @XmlAttribute: 将一个字段或属性映射到 XML 元素的属性。
  • @XmlAccessorType: 控制哪些字段/属性会被序列化,常用值 XmlAccessType.FIELD 表示序列化所有非 static 和非 transient 的字段。
  • @XmlElementWrapper: 用于包装一个列表类型的字段,使其在 XML 中表现为一个父元素,列表中的每个元素作为其子元素。

核心类

  • javax.xml.bind.JAXBContext: 上下文对象,是 JAXB API 的入口点,用于管理 XML/Java 绑定信息。
  • javax.xml.bind.Marshaller: 负责将 Java 对象序列化为 XML。
  • javax.xml.bind.Unmarshaller: 负责将 XML 反序列化为 Java 对象。

完整示例:使用 JAXB 序列化和反序列化

假设我们有一个 User 类,我们想将它转换为 XML,然后再从 XML 恢复成 User 对象。

Java中XML序列化与反序列化如何实现?-图2
(图片来源网络,侵删)

步骤 1: 创建 Java 类并添加 JAXB 注解

我们定义 User 类,为了简化,我们使用 Lombok 来自动生成 getter, setter, toString 等方法,但即使不使用 Lombok,手动添加这些方法也可以。

User.java

import javax.xml.bind.annotation.*;
import java.util.List;
// 1. 指定这个类是 XML 的根元素,名称为 "user"
@XmlRootElement(name = "user")
// 2. 指定访问级别为 FIELD,即直接序列化/反序列化字段
@XmlAccessorType(XmlAccessType.FIELD)
public class User {
    // 3. 映射为 XML 元素,名称为 "id"
    @XmlElement(name = "id")
    private int id;
    // 4. 映射为 XML 元素,名称为 "name"
    @XmlElement(name = "name")
    private String name;
    // 5. 使用 @XmlElementWrapper 包装列表,列表元素名为 "role"
    @XmlElementWrapper(name = "roles")
    @XmlElement(name = "role")
    private List<String> roles;
    // 默认构造函数是 JAXB 反序列化所必需的
    public User() {
    }
    // 带参数的构造函数
    public User(int id, String name, List<String> roles) {
        this.id = id;
        this.name = name;
        this.roles = roles;
    }
    // Getter 和 Setter 方法 (Lombok 会自动生成)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public List<String> getRoles() {
        return roles;
    }
    public void setRoles(List<String> roles) {
        this.roles = roles;
    }
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", roles=" + roles +
                '}';
    }
}

步骤 2: 创建工具类进行序列化和反序列化

为了方便使用,我们创建一个 JAXBUtils 工具类。

JAXBUtils.java

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
public class JAXBUtils {
    /**
     * 将 Java 对象序列化为 XML 字符串
     * @param obj 要序列化的对象
     * @param clazz 对象的 Class 对象
     * @return XML 字符串
     */
    public static <T> void toFile(T obj, String filePath) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(obj.getClass());
        Marshaller marshaller = context.createMarshaller();
        // 格式化输出,使 XML 更易读
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        // 设置编码
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        // 输出到文件
        marshaller.marshal(obj, new java.io.File(filePath));
    }
    /**
     * 将 Java 对象序列化为 XML 字符串
     * @param obj 要序列化的对象
     * @param clazz 对象的 Class 对象
     * @return XML 字符串
     */
    public static <T> String toXml(T obj) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(obj.getClass());
        Marshaller marshaller = context.createMarshaller();
        // 格式化输出,使 XML 更易读
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        // 设置编码
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        Writer writer = new StringWriter();
        // 将对象序列化到 writer 中
        marshaller.marshal(obj, writer);
        return writer.toString();
    }
    /**
     * 将 XML 字符串反序列化为 Java 对象
     * @param xml XML 字符串
     * @param clazz 目标对象的 Class 对象
     * @return 反序列化后的 Java 对象
     */
    public static <T> T fromXml(String xml, Class<T> clazz) throws JAXBException {
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = context.createUnmarshaller();
        // 从 StringReader 中读取 XML 并进行反序列化
        return (T) unmarshaller.unmarshal(new StringReader(xml));
    }
}

步骤 3: 编写主程序进行测试

Main.java

import java.util.Arrays;
public class Main {
    public static void main(String[] args) {
        // 1. 创建一个 User 对象
        User user = new User(1, "张三", Arrays.asList("管理员", "开发者"));
        try {
            // 2. 序列化:将 User 对象转换为 XML 字符串
            String xmlString = JAXBUtils.toXml(user);
            System.out.println("序列化后的 XML:");
            System.out.println(xmlString);
            // 3. 反序列化:将 XML 字符串转换回 User 对象
            User deserializedUser = JAXBUtils.fromXml(xmlString, User.class);
            System.out.println("\n反序列化后的 User 对象:");
            System.out.println(deserializedUser);
            // 4. 序列化到文件
            JAXBUtils.toFile(user, "user.xml");
            System.out.println("\nXML 文件 'user.xml' 已生成。");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果

执行 Main.java,控制台输出如下:

序列化后的 XML:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user>
    <id>1</id>
    <name>张三</name>
    <roles>
        <role>管理员</role>
        <role>开发者</role>
    </roles>
</user>
反序列化后的 User 对象:
User{id=1, name='张三', roles=[管理员, 开发者]}
XML 文件 'user.xml' 已生成。

项目根目录下会生成一个 user.xml 文件,内容与控制台打印的 XML 字符串一致。


处理复杂 XML 结构

对于更复杂的 XML,例如包含命名空间,JAXB 也能很好地处理。

示例:带命名空间的 XML

假设我们有如下 XML:

<ns2:user xmlns:ns2="http://www.example.com/users">
    <ns2:id>101</ns2:id>
    <ns2:name>李四</ns2:name>
</ns2:user>

对应的 Java 类可以这样定义:

import javax.xml.bind.annotation.*;
@XmlRootElement(name = "user", namespace = "http://www.example.com/users")
@XmlAccessorType(XmlAccessType.FIELD)
public class UserWithNamespace {
    @XmlElement(namespace = "http://www.example.com/users")
    private int id;
    @XmlElement(namespace = "http://www.example.com/users")
    private String name;
    // 构造函数、getter、setter...
}

JAXB 会自动处理这些命名空间信息。


常见问题与注意事项

  1. Java 9+ 模块化问题: 从 Java 9 开始,JAXB 不再是默认的内置模块,如果你使用 Java 9 或更高版本,并且开启了模块系统(module-info.java),可能会遇到 java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException 错误。 解决方案:你需要手动添加 JAXB 依赖,如果你使用 Maven,在 pom.xml 中添加:

    <dependencies>
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.1</version>
        </dependency>
    </dependencies>

    对于 Java 11+,jaxb-runtime 是必需的。

  2. 必须有默认构造函数: JAXB 在反序列化时需要通过反射调用类的无参(默认)构造函数来创建对象实例,你的 Java 类必须提供一个无参的构造函数,即使它是 public 的。

  3. 循环引用问题: 如果对象图中有循环引用(A 对象包含 B 对象,B 对象又引用了 A 对象),直接序列化会导致无限循环,最终栈溢出。 解决方案:可以使用 @XmlTransient 注解来标记某个字段,使其不被序列化。

  4. 性能: 对于非常大型或性能要求极高的场景,JAXB 可能不是最佳选择,可以考虑使用性能更高的 XML 库,如 JacksonGson(它们也支持 XML 格式,通常通过 jackson-dataformat-xml 等扩展包实现)。


JAXB vs. Jackson XML

特性 JAXB Jackson XML (jackson-dataformat-xml)
来源 Java 标准 (JSR-222) 第三方库 (Jackson 生态系统)
依赖 Java 6+ 内置 (Java 9+ 需手动添加) 需要手动添加 Maven/Gradle 依赖
易用性 注解清晰,API 简单 注解丰富,与 JSON 库 API 风格一致
功能 功能强大,稳定 功能极其强大,性能通常更好,支持更多特性
集成 与 JAX-WS 等标准 Java EE 技术集成度高 与 Spring Boot 等现代框架集成度高,更灵活
  • 如果你只需要一个简单、可靠的解决方案,并且不想引入额外依赖(在 Java 8 环境下),JAXB 是完美的选择
  • 如果你正在使用 Spring Boot,或者对性能、灵活性有更高要求,Jackson XML 是更现代、更通用的选择
分享:
扫描分享到社交APP
上一篇
下一篇