@DateTimeFormat 与 @JsonFormat 详解
@DateTimeFormat 与 @JsonFormat 详解
适用场景:Spring Boot + Jackson 项目中的时间类型参数处理
一、概述
在 Spring Boot 项目中,处理时间类型(LocalDateTime、Date等)时,开发者常常遇到两个注解:
@DateTimeFormat:来自 Spring 框架@JsonFormat:来自 Jackson 框架
两者职责不同、作用时机不同,混用或单用都可能导致时间解析失败。本文将系统梳理两者的区别与最佳实践。
二、@DateTimeFormat — 入参解析印
2.1 基本信息
| 属性 | 说明 |
|---|---|
| 归属 | org.springframework.format.annotation |
| 框架 | Spring MVC |
| 职责 | 将 HTTP 请求入参中的字符串 → 时间对象 |
2.2 适用场景
| 场景 | 是否生效 |
|---|---|
@RequestParam查询参数 | ✅ 生效 |
@ModelAttribute表单提交 | ✅ 生效 |
| 实体类字段(Form 提交) | ✅ 生效 |
@RequestBodyJSON 字段 | ❌不生效(由 Jackson 处理) |
2.3 使用示例
// GET /user/list?createTime=2024-01-15 10:30:00@GetMapping("/list")publicApiResult<List<UserVO>>list(@RequestParam@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")LocalDateTimecreateTime){// ...}@DatapublicclassUserQueryDTO{// Form 表单提交时,Spring MVC 会按此格式解析字符串@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")privateLocalDateTimestartTime;}三、@JsonFormat — JSON 序列化/反序列化印
3.1 基本信息
| 属性 | 说明 |
|---|---|
| 归属 | com.fasterxml.jackson.annotation |
| 框架 | Jackson |
| 职责 | 控制 JSON序列化(对象 → 字符串)与反序列化(字符串 → 对象)的格式 |
3.2 适用场景
| 场景 | 是否生效 |
|---|---|
@RequestBodyJSON 反序列化 | ✅ 生效 |
@ResponseBodyJSON 序列化(响应输出) | ✅ 生效 |
@RequestParam查询参数 | ❌不生效 |
| Form 表单提交 | ❌不生效 |
3.3 使用示例
@DatapublicclassUserVO{// 序列化输出 + JSON 反序列化输入,均按此格式处理@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimecreateTime;}3.4 重要参数说明
| 参数 | 说明 | 示例 |
|---|---|---|
pattern | 时间格式 | "yyyy-MM-dd HH:mm:ss" |
timezone | 时区(不指定可能导致时间偏移 8 小时) | "Asia/Shanghai"/"GMT+8" |
shape | 序列化形状 | JsonFormat.Shape.STRING |
四、核心对比
| 维度 | @DateTimeFormat | @JsonFormat |
|---|---|---|
| 归属框架 | Spring MVC | Jackson |
| 作用方向 | 入参解析(String → Date) | 序列化 + 反序列化 |
| 适用场景 | Form / Query 参数 | JSON Body 入参 + 响应输出 |
| 时区参数 | ❌ 无 | ✅timezone |
| 生效位置 | 方法参数、字段 | 字段、getter/setter |
五、最佳实践:双印加持
对于实体类/DTO 字段,同时加上两个注解,覆盖所有入参场景:
@DatapublicclassProjectPageDTO{/** * 开始时间 * - @DateTimeFormat:处理 Query/Form 入参(Spring MVC 解析) * - @JsonFormat:处理 JSON Body 入参 + 响应输出(Jackson 处理) */@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimestartTime;@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimeendTime;}六、全局配置(更优解)
在 Spring Boot 项目中,可通过全局 Jackson 配置统一处理序列化格式,避免在每个字段上重复添加@JsonFormat。
6.1 全局 Jackson 配置
@ConfigurationpublicclassJacksonConfig{@BeanpublicJackson2ObjectMapperBuilderCustomizerjsonCustomizer(){returnbuilder->{// 全局时区builder.timeZone(TimeZone.getTimeZone("Asia/Shanghai"));// LocalDateTime 序列化格式builder.serializers(newLocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// LocalDateTime 反序列化格式builder.deserializers(newLocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));};}}6.2 全局配置后的使用策略
| 场景 | 是否需要注解 |
|---|---|
| 普通时间字段,格式与全局一致 | ❌ 无需添加@JsonFormat |
特殊格式字段(如只需日期yyyy-MM-dd) | ✅ 需要单独加@JsonFormat覆盖 |
| Query 参数(所有场景) | ✅ 仍需手动加@DateTimeFormat |
七、常见陷阱
陷阱一:仅加 @JsonFormat,Query 参数报错
// ❌ GET 请求传参时解析失败@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")privateLocalDateTimecreateTime;错误信息:Failed to convert value of type 'String' to required type 'LocalDateTime'
陷阱二:仅加 @DateTimeFormat,JSON Body 格式不受控
// ❌ JSON 入参和响应依赖全局 Jackson 配置,字段级别无法控制格式@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")privateLocalDateTimecreateTime;陷阱三:@JsonFormat 未指定 timezone 导致时间偏移
// ❌ 可能导致时间偏移 8 小时(UTC vs Asia/Shanghai)@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")privateLocalDateTimecreateTime;// ✅ 明确指定时区@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimecreateTime;陷阱四:两个注解的 pattern 不一致
// ❌ 格式不统一,不同入参方式解析结果不同,难以排查@DateTimeFormat(pattern="yyyy-MM-dd")@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimecreateTime;八、完整代码示例
以下是一个涵盖各场景的完整示例:
/** * 项目分页查询 DTO */@DatapublicclassProjectPageDTO{/** 项目名称(模糊搜索) */privateStringprojectName;/** * 创建时间起(双印加持) * Query 参数 + JSON Body 均支持 */@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimecreateTimeStart;/** * 创建时间止 */@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimecreateTimeEnd;}/** * 项目响应 VO */@DatapublicclassProjectVO{privateLongprojectId;privateStringprojectName;/** * 创建时间 * 全局 JacksonConfig 已配置默认格式时,此注解可省略 * 需要特殊格式时才单独加 */@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")privateLocalDateTimecreateTime;}九、总结
HTTP 请求 │ ┌─────────┴─────────┐ │ │ Query/Form JSON Body │ │ @DateTimeFormat @JsonFormat (Spring MVC 解析) (Jackson 解析) │ │ └─────────┬─────────┘ │ 时间对象 ✅| 注解 | 核心职责 | 一句话记忆 |
|---|---|---|
@DateTimeFormat | Query/Form 入参解析 | “URL 和表单用我” |
@JsonFormat | JSON 序列化/反序列化 | “JSON 进出用我” |
最佳实践:
- 全局配置
JacksonConfig统一 JSON 时间格式与时区- 所有时间类型字段标注
@DateTimeFormat,覆盖 Query 参数场景- 仅在需要特殊格式时,才在字段上单独加
@JsonFormat覆盖全局配置
本文基于 Spring Boot 2.7.x + Jackson 2.13.x 编写,适用于 Java 8+ 项目。
