1. 简介

本文将深入探讨如何使用JAXB处理不同格式的日期反序列化(unmarshal)问题。

我们将从XML Schema默认的日期格式讲起,逐步过渡到自定义格式的处理方式,并重点分析实际开发中容易踩坑的关键点。✅

核心内容包括:

  • XML Schema与Java日期类型的映射关系
  • 默认格式下的日期反序列化
  • 自定义格式的适配方案(XmlAdapter
  • Java 8新时间API的支持

2. XML Schema 与 Java 类型映射

在使用JAXB处理日期前,必须清楚XML Schema中的日期类型是如何映射到Java类型的。

根据官方规范,以下三种Schema类型会被映射为Java中的 XMLGregorianCalendar

Schema 类型 默认格式 Java 映射类型
xsd:date YYYY-MM-DD XMLGregorianCalendar
xsd:time hh:mm:ss XMLGregorianCalendar
xsd:dateTime YYYY-MM-DDThh:mm:ss XMLGregorianCalendar

⚠️ 注意:dateTime 格式中使用了 T 作为日期和时间部分的分隔符,这是W3C标准规定,不可省略。

这意味着,如果你的XML中日期写成 1979-10-21 03:31:12(空格分隔),直接使用默认方式反序列化会失败 ❌

3. 使用默认Schema日期格式

我们以 xsd:dateTime 为例,演示标准格式下的反序列化流程。

示例XML

<book>
    <title>Effective Java</title>
    <published>1979-10-21T03:31:12</published>
</book>

对应的Java实体类

@XmlRootElement(name = "book")
public class Book {

    @XmlElement(name = "title", required = true)
    private String title;

    @XmlElement(name = "published", required = true)
    private XMLGregorianCalendar published;

    @Override
    public String toString() {
        return "[title: " + title + "; published: " + published.toString() + "]";
    }
}

反序列化方法

public static Book unmarshalDates(InputStream inputFile) throws JAXBException {
    JAXBContext jaxbContext = JAXBContext.newInstance(Book.class);
    Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
    return (Book) jaxbUnmarshaller.unmarshal(inputFile);
}

执行后输出:

[title: Effective Java; published: 1979-10-21T03:31:12]

✅ 小贴士:虽然默认映射到 XMLGregorianCalendar,但你也可以通过配置让JAXB支持 java.util.DateCalendar,具体参考JAXB用户指南。

4. 使用自定义日期格式

当你的XML日期格式不符合Schema标准(比如用空格代替T),就需要自定义适配器。

4.1 编写自定义 XmlAdapter

目标:支持 YYYY-MM-DD HH:mm:ss 格式,并映射到 java.util.Date

public class DateAdapter extends XmlAdapter<String, Date> {

    private static final String CUSTOM_FORMAT_STRING = "yyyy-MM-dd HH:mm:ss";

    @Override
    public String marshal(Date v) {
        return new SimpleDateFormat(CUSTOM_FORMAT_STRING).format(v);
    }

    @Override
    public Date unmarshal(String v) throws ParseException {
        return new SimpleDateFormat(CUSTOM_FORMAT_STRING).parse(v);
    }
}

⚠️ 踩坑提醒:SimpleDateFormat 不是线程安全的!这里每次调用都创建新实例,避免多线程环境下出现数据错乱。

4.2 XmlAdapter 工作原理

XmlAdapter<String, Date> 的两个泛型参数含义:

  • String:XML中的值类型(value type),JAXB原生支持
  • Date:Java对象中的绑定类型(bound type)

工作流程:

  1. 反序列化时:XML → String → unmarshal() → Date
  2. 序列化时:Date → marshal() → String → XML

4.3 通过注解集成适配器

使用 @XmlJavaTypeAdapter 将适配器绑定到字段:

@XmlRootElement(name = "book")
public class BookDateAdapter {

    @XmlElement(name = "title", required = true)
    private String title;

    @XmlElement(name = "published", required = true)
    @XmlJavaTypeAdapter(DateAdapter.class)  // 关键注解
    private Date published;

    // getter/setter 省略
}

测试输入XML:

<book>
    <title>Thinking in Java</title>
    <published>1979-11-28 02:31:32</published>
</book>

输出结果:

[title: Thinking in Java; published: Wed Nov 28 02:31:32 CST 1979]

完美支持空格分隔格式 ✅

5. Java 8 时间API支持

Java 8引入了全新的 java.time 包,推荐使用 LocalDateTime 替代旧日期类型。

5.1 编写 LocalDateTime 适配器

public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {

    private DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public String marshal(LocalDateTime dateTime) {
        return dateTime.format(dateFormat);
    }

    @Override
    public LocalDateTime unmarshal(String dateTime) {
        return LocalDateTime.parse(dateTime, dateFormat);
    }
}

✅ 优势:

  • DateTimeFormatter 是线程安全的,可复用实例
  • 与Java 8时间API无缝集成
  • 性能更好,API更清晰

5.2 集成到实体类

@XmlRootElement(name = "book")
public class BookLocalDateTimeAdapter {

    @XmlElement(name = "title", required = true)
    private String title;

    @XmlElement(name = "published", required = true)
    @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    private LocalDateTime published;

    // getter/setter 省略

    @Override
    public String toString() {
        return "[title: " + title + "; published: " + published + "]";
    }
}

输出:

[title: Clean Code; published: 1979-11-28T02:31:32]

注意:LocalDateTime.toString() 默认使用 T 分隔,这是标准行为,无需担心。

6. 总结

本文系统讲解了JAXB处理日期的三种场景:

方案 适用场景 是否推荐
默认 XMLGregorianCalendar 标准Schema格式 ⚠️ 仅临时使用
自定义 XmlAdapter + Date 非标准格式,老项目 ✅ 可用但注意线程安全
XmlAdapter + LocalDateTime 新项目,Java 8+ ✅✅✅ 强烈推荐

最佳实践建议:

  1. 新项目优先使用Java 8时间API
  2. 自定义格式必须通过 XmlAdapter 处理
  3. 避免共享 SimpleDateFormat 实例
  4. 保持XML格式一致性,减少适配成本

所有示例代码已托管至 GitHub:https://github.com/baeldung/tutorials/tree/master/jaxb


原始标题:Unmarshalling Dates Using JAXB | Baeldung