Java 8 时间类型序列化翻车现场:从 LocalDateTime 不支持的异常到一劳永逸的 ObjectMapper 配置
·
一、问题复现
java.util.concurrent.CompletionException:
java.lang.IllegalArgumentException:
Java 8 date/time type `java.time.LocalDateTime` not supported by default:
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
堆栈顶行指向我们「通用 Excel 导出」工具类:
java
private static Map<String, Object> beanToMap(Object bean) {
return objectMapper.convertValue(bean, Map.class); // ← 220 行炸雷
}
二、根因定位
-
导出逻辑为了通用,把任意 Bean 先转成
Map<String,Object>,再按字段顺序写 Excel。 -
业务对象
ColumnInfoResponse里带有LocalDateTime createTime。 -
Jackson 默认不带 Java 8 时间模块,遇到
java.time.*直接抛InvalidDefinitionException。 -
异常被
CompletableFuture捕获,包装成CompletionException,线程池线程直接退出,前端收到「导出失败」。
三、最小复现
ObjectMapper mapper = new ObjectMapper();
mapper.convertValue(new ColumnInfoResponse(), Map.class);
// 复现一模一样异常
四、两种根治方案
| 方案 | 步骤 | 适用场景 |
|---|---|---|
| A. 直接 new + 注册模块(本文采用) | ① 引入 jackson-datatype-jsr310② new ObjectMapper().registerModule(new JavaTimeModule()) | 工具类独立,不依赖 Spring |
| B. 复用 Spring 全局 Bean | ① 配置类声明 @Bean ObjectMapper② @PostConstruct 把 Spring 实例赋给 static 字段 | 已使用 Spring Boot,方便统一管理 |
下面给出完整可拷贝代码。
五、代码级解决方案
1. Maven 依赖
xml
复制
<!-- 与 jackson-databind 版本保持一致 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.15.2</version>
</dependency>
2. 工具类改造
@Slf4j
@Service
public class CommonExportService<T extends BasePageQry, R extends BaseDownloadResponse> {
/* 静态工具 Mapper,一次性初始化 */
private static final ObjectMapper objectMapper = createObjectMapper();
private static ObjectMapper createObjectMapper() {
return new ObjectMapper()
.registerModule(new JavaTimeModule()) // 支持 LocalDateTime
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 返回 ISO-8601 字符串
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
}
/** 任意 Bean → Map,兼容 null */
private static Map<String, Object> beanToMap(Object bean) {
if (bean == null) {
return new HashMap<>();
}
return objectMapper.convertValue(bean, Map.class);
}
/** 写 Excel 核心逻辑 */
private static void writeSheetData(PageIndicator downloadData,
List<String> fieldKeys,
ExcelWriter writer,
WriteSheet sheet) {
List<List<String>> excelData = new ArrayList<>();
for (Object row : downloadData.getRecords()) {
Map<String, Object> rowMap = beanToMap(row);
List<String> rowData = new ArrayList<>();
for (String field : fieldKeys) {
rowData.add(getCellValue(rowMap.get(field)));
}
excelData.add(rowData);
}
writer.write(excelData, sheet);
}
private static String getCellValue(Object val) {
return val == null ? "" : val.toString();
}
}
3. 验证
ColumnInfoResponse resp = new ColumnInfoResponse();
resp.setCreateTime(LocalDateTime.now());
Map<String, Object> map = CommonExportService.beanToMap(resp);
System.out.println(map.get("createTime")); // 2025-12-04T21:30:45
控制台正常输出 ISO 格式字符串,Excel 导出恢复绿灯。
六、容易踩的坑
| 坑点 | 说明 |
|---|---|
| 版本不一致 | jackson-databind 与 jackson-datatype-jsr310 版本差一位就会出 NoSuchMethodError |
| 时间变时间戳 | 忘记关闭 WRITE_DATES_AS_TIMESTAMPS,Excel 出现 1700000000000 这种数字 |
| 循环依赖 | 用 Spring 注入时,别把 ObjectMapper 再 @Autowired 到静态字段里,避免构造期循环 |
七、一句话总结
只要记住:
「Java 8 时间类型 → 必须注册 JavaTimeModule」
从此 LocalDateTime、LocalDate、Instant 都能被 Jackson 当成普通字段无缝转换,导出、缓存、RPC 全场景通用。
八、参考资源
-
Spring Boot 自动配置
Jackson2ObjectMapperBuilder源码
更多推荐




所有评论(0)