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.Date
或 Calendar
,具体参考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)
工作流程:
- 反序列化时:XML → String →
unmarshal()
→ Date - 序列化时: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+ | ✅✅✅ 强烈推荐 |
最佳实践建议:
- 新项目优先使用Java 8时间API
- 自定义格式必须通过
XmlAdapter
处理 - 避免共享
SimpleDateFormat
实例 - 保持XML格式一致性,减少适配成本
所有示例代码已托管至 GitHub:https://github.com/baeldung/tutorials/tree/master/jaxb