Java JAXB怎么处理日期格式 @XmlJavaTypeAdapter

@XmlJavaTypeAdapter 是 JAXB 处理日期格式最常用且推荐的方式,用于可控双向转换 java.util.Date、LocalDateTime 等类型与 XML 字符串,解决默认序列化兼容性问题。

Java JAXB 处理日期格式时,@XmlJavaTypeAdapter 是最常用且推荐的方式——它能将 java.util.DateLocalDateTimeLocalDate 等类型与 XML 字符串之间做可控的双向转换,避免默认序列化(如毫秒时间戳或平台相关格式)带来的兼容性问题。

为什么需要 @XmlJavaTypeAdapter

JAXB 默认对 Date 类型使用 javax.xml.bind.DatatypeConverter,输出类似 2025-05-20T14:30:00.123+08:00 的 ISO 8601 格式;而对 LocalDateTime 等 Java 8 时间类则直接报错(不支持)。用 @XmlJavaTypeAdapter 可以:

  • 统一指定输出格式(如 yyyy-MM-dd HH:mm:ss
  • 适配不同时间类型(LocalDateTime / LocalDate / ZonedDateTime
  • 处理时区、空值、解析异常等边界情况

适配 LocalDateTime(推荐 Java 8+)

写一个自定义 Adapter:

public class LocalDateTimeAdapter extends XmlAdapter {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    @Override
    public LocalDateTime unmarshal(String s) throws Exception {
        if (s == null || s.trim().isEmpty()) return null;
        return LocalDateTime.parse(s.trim(), FORMATTER);
    }

    @Override
    public String marshal(LocalDateTime dateTime) throws Exception {
        return (dateTime == null) ? null : dateTime.format(FORMATTER);
    }
}

在字段上使用:

public class Order {
    @XmlElement
    @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    private LocalDateTime createTime;
    // getter/setter...
}

适配 Date(兼容老项目)

若仍用 java.util.Date,Adapter 可这样写:

public class DateAdapter extends XmlAdapter {
    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    static { FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+8")); }

    @Override
    public Date unmarshal(String s) throws Exception {
        if (s == null || s.trim().isEmpty()) return null;
        return FORMAT.parse(s.trim());
    }

    @Override
    public String marshal(Date date) throws Exception {
        return (date == null) ? null : FORMAT.format(date);
    }
}

注意:SimpleDateFormat 非线程安全,建议每个方法内新建实例,或用 ThreadLocal 封装(生产环境更稳妥)。

全局注册(避免重复标注)

如果多个类都用相同格式,可在 JAXBContext 创建时全局绑定:

Map props = new HashMap<>();
props.put("com.sun.xml.bind.defaultNamespacePrefix", "ns");
JAXBContext ctx = JAXBContext.newInstance(Order.class);
// 注册全局 adapter(需配合 @XmlJavaTypeAdapters)
// 或改用 Jakarta EE 方式:通过 package-info.java 声明

更简洁的方式是在包级声明 package-info.java

@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(value = LocalDateTimeAdapter.class, type = LocalDateTime.class),
    @XmlJavaTypeAdapter(value = DateAdapter.class, type = Date.class)
})
package com.example.model;

这样该包下所有字段自动生效,无需每个字段加注解。

基本上就这些。关键点是:选对时间类型、写好 Adapter 的 marshal/unmarshal 逻辑、注意线程安全和空值处理。不复杂但容易忽略细节。