1)消息转换器是干嘛的?
你可以把一次接口调用想成:
前端说 JSON → 后端要变成 Java 对象
后端算完得到 Java 对象 → 要变成 JSON 再返回给前端
这个“互相翻译”的工作,就是 HttpMessageConverter 做的。
- 请求进来:把请求体(Body)里的 JSON → 转成
DTO/Entity - 响应出去:把返回值对象
VO/Entity→ 转成 JSON
所以你写的:
@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO dto)
这里的 @RequestBody 能把 JSON 变 DTO,就是消息转换器在干活。
2)它什么时候会被用到?
只要你满足这两种情况,基本就会走消息转换器:
✅ 进来用(反序列化)
- Controller 参数上有
@RequestBody - 或请求是
Content-Type: application/json
例子:JSON → EmployeeLoginDTO
✅ 出去用(序列化)
- Controller 返回对象(不是 view)
- 或类上有
@RestController
例子:Result<EmployeeLoginVO> → JSON
3)为什么要“扩展消息转换器”?你这个案例解决啥问题?
你扩展的是 MappingJackson2HttpMessageConverter,它负责:
Java对象 ⇄ JSON 的转换(用的是 Jackson)
默认 Jackson 对时间(LocalDateTime 等)序列化出来可能是:
- 时间戳、数组形式、或带秒/时区不一致
- 前端想要统一格式:比如
"yyyy-MM-dd HH:mm"
所以你们做了一个 自定义 ObjectMapper(JacksonObjectMapper) ,专门规定:
LocalDateTime用"yyyy-MM-dd HH:mm"LocalDate用"yyyy-MM-dd"LocalTime用"HH:mm:ss"
这样你后端所有返回时间字段都会“长得一致”,前端也不用各种兼容。
4)为什么要 converters.add(0, converter) 放到最前面?
Spring 会维护一堆消息转换器(你看到默认 8 个)。
当它要写出响应时,会按顺序找:
谁 支持 当前返回类型 + 当前响应媒体类型(比如 application/json)
找到第一个能用的就用它
如果你不放在最前面,可能会被默认的 Jackson converter 抢先用掉,你的时间格式配置就不生效或不稳定。
放到索引 0 的意思就是:
我这个翻译官优先级最高,先用我的规则翻译
5)extendMessageConverters 和 configureMessageConverters 区别(很关键)
extendMessageConverters(...):在默认转换器基础上加/改(推荐,安全)configureMessageConverters(...):你全接管,默认的都没了(容易出坑)
你们用的是 extend,属于“加一条自己的规则”,这就是推荐做法。
6)你打断点看到的“8个转换器”大概是啥?
不用背,只要知道它们是“不同语言的翻译官”,比如:
- String 翻译官:处理纯文本
- ByteArray 翻译官:处理二进制
- Form 表单翻译官:处理
application/x-www-form-urlencoded - Jackson 翻译官:处理 JSON(你现在是增强它)
7)你该怎么验证它生效?
最简单的验证方式:
-
找一个接口返回对象里有
LocalDateTime字段(比如创建时间) -
调接口看响应 JSON:
- 是否是
"2025-12-28 10:30"这种格式(按你配置的"yyyy-MM-dd HH:mm")
- 是否是
如果没变,通常是:
- converter 没放到最前面
- 你没用
JacksonObjectMapper(或者被别的 ObjectMapper 覆盖) - 返回的时间字段不是
LocalDateTime(比如是Date或 String)