Java如何将java.util.Date正确序列化到XML (JAXB)

Java中JAXB序列化java.util.Date默认调用toString(),应通过XmlAdapter自定义格式为ISO 8601;推荐改用Java 8时间API如LocalDateTime并配适配器,避免Date的线程安全以及时区问题。

Java中使用JAXB将java.util.Date序列化为XML时,默认行为往往不符合预期:它会调用Date.toString()生成类似Mon Jan 01 00:00:00 CST 2025的字符串,既不规范也不便于解析。正确做法是通过自定义适配器(XmlAdapter)统一控制格式,确保输出ISO 8601标准(如2025-01-01T00:00:00)或符合业务要求的时间格式。

使用XmlAdapter统一处理Date序列化

JAXB本身不直接支持java.util.Date的格式化,但允许通过@XmlJavaTypeAdapter绑定自定义转换逻辑。核心是继承XmlAdapter,在marshal()中将Date转为格式化字符串,在unmarshal()中反向解析。

  • 创建适配器类,内部使用SimpleDateFormat(注意线程安全,建议每次新建或使用ThreadLocal
  • 在实体字段上添加@XmlJavaTypeAdapter(DateAdapter.class)
  • 若需全局生效,可在@XmlSchemaJAXBContext构建时注册,但字段级标注更清晰可控

推荐使用Java 8时间API替代Date(更健壮)

java.util.Date已属遗留类,JAXB原生不支持LocalDateTimeZonedDateTime等,但可通过适配器轻松支持,且语义更明确、线程安全、无时区歧义。

  • 例如用LocalDateTimeAdapter配合DateTimeFormatter.ISO_LOCAL_DATE_TIME
  • 实体中改用LocalDateTime字段,标注@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
  • 如需带时区,优先选OffsetDateTime而非Date,避免系统默认时区干扰

避免常见陷阱

直接在Date字段上加@XmlSchemaType(name = "dateTime")无效——该注解仅对JAXB内置类型(如XMLGregorianCalendar)起作用;对Date无格式影响。

  • 不要在适配器中复用非线程安全的SimpleDateFormat实例(尤其在Web容器中)
  • 反序列化时注意时区:SimpleDateFormat默认按JVM时区解析,建议显式设setTimeZone(TimeZone.getTimeZone("UTC"))
  • 若服务需兼容旧客户端,可保留Date字段但强制输出为UTC时间字符串,避免本地时区偏差

一个简洁可用的DateAdapter示例

以下适配器将Date序列化为yyyy-MM-dd'T'HH:mm:ss.SSS格式(毫秒级,无时区),适用于大多数内部系统交互:


public class DateAdapter extends XmlAdapter {
  private static final String PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS";
  @Override
  public String marshal(Date date) throws Exception {
    if (date == null) return null;
    return new SimpleDateFormat(PATTERN).format(date);
  }
  @Override
  public Date unmarshal(String str) throws Exception {
    if (str == null || str.trim().isEmpty()) return null;
    return new SimpleDateFormat(PATTERN).parse(str);
  }
}