为了彻底搞清,我们把 Spring Boot 拆分成 "服务端(Server)" 和 "客户端(Client)" 两个角色来看。
情况一:Spring Boot 作为服务端 (最常见的 Controller 场景)
这是你写 @RestController 接收浏览器请求的时候。
-
处理请求 (Request) ------ 接收
- 实际动作 :是 JSON/XML 转 Java 对象。
- 原理 :前端发来
{"name":"A"}(JSON字符串) -> Converter (read方法) ->User对象。 - 特殊 :如果前端发来表单 (
a=1&b=2),通常是 Tomcat 转,Converter 不怎么管(除非用@RequestBody MultiValueMap)。
-
处理响应 (Response) ------ 发送
- 实际动作 :Java 对象 转 JSON/XML。
- 原理 :
return user;-> Converter (write方法) ->{"name":"A"}(JSON字符串) -> 发给浏览器。
情况二:Spring Boot 作为客户端 (RestTemplate 远程调用场景)
这是你写 restTemplate.postForObject(...) 去调用别人的时候。
-
发起请求 (Request) ------ 发送
- 实际动作 :
- 如果你传
User对象 ->MappingJackson2HttpMessageConverter把它转成 JSON 字符串 发出去。 - 如果你传
MultiValueMap->FormHttpMessageConverter把它转成 表单字符串 (a=1&b=2) 发出去。
- 如果你传
- 实际动作 :
-
接收响应 (Response) ------ 接收
- 实际动作 :JSON/XML 转 Java 对象。
- 原理 :别人回给你
{"status":"ok"}-> Converter (read方法) -> 你拿到String或Result对象。
终极总结表(一张表看懂所有方向)
消息转换器 (HttpMessageConverter) 的核心其实只有两个动作:写 (Write) 和 读 (Read)。
| 场景 | 动作方向 | 转换逻辑 | 典型 Converter |
|---|---|---|---|
| Server 响应浏览器 | 写 (Write) | Java 对象 -> JSON/XML | Jackson |
| Client 发送给别人 | 写 (Write) | Java 对象 -> JSON/XML | Jackson |
| Client 发送给别人 | 写 (Write) | MultiValueMap -> 表单String | Form |
| Server 接收浏览器 | 读 (Read) | JSON/XML -> Java 对象 | Jackson |
| Client 接收别人 | 读 (Read) | JSON/XML -> Java 对象 | Jackson |
处理请求,处理响应,发送请求,接收响应分别都是怎么判断要用哪个转换器的、
Spring 内部维护了一个 HttpMessageConverter 列表 (List)。
无论是 Server 端还是 Client 端,判断"用哪个转换器"的逻辑,本质上都是一个 "遍历列表 + 面试" 的过程。
核心判断标准只有两个要素:
- Java 类型 :涉及的 Java 对象是谁?(比如
User.class,String.class,byte[].class) - Media 类型 (MIME Type) :涉及的 HTTP 数据格式是什么?(比如
application/json,text/plain)
下面我们分这 4 种场景详细拆解:
1. 处理请求 (Server 端读:@RequestBody)
场景:前端发来 POST 请求,Spring MVC 要把 Body 转成 Java 对象。
-
输入条件:
- Java 目标类型 :Controller 方法参数的类型(如
User.class)。 - Media 类型 :HTTP 请求头里的
Content-Type(如application/json)。
- Java 目标类型 :Controller 方法参数的类型(如
-
判断逻辑 :
Spring 会遍历所有 Converter,挨个问(调用
canRead方法):"喂,你能读取
application/json格式的数据,并把它转成User类型吗?"StringHttpMessageConverter:我看它是User类型,我不行。ByteArrayHttpMessageConverter:我看它是User类型,我不行。MappingJackson2HttpMessageConverter:我可以! (因为它支持application/json且支持通用 Object)。
-
结论:Jackson 胜出,开始干活。
2. 处理响应 (Server 端写:@ResponseBody)
场景 :Controller 返回 Java 对象,Spring MVC 要把它转成 HTTP Body 返回给浏览器。
注意 :这是最复杂的 "内容协商" 过程。
- 输入条件 :
- Java 源类型 :Controller 方法的返回值类型(如
User.class)。 - Media 类型 :HTTP 请求头里的
Accept(客户端想要什么,如application/xml)。
- Java 源类型 :Controller 方法的返回值类型(如
- 判断逻辑 :
- 统计服务端能力 :Spring 先问一遍所有 Converter:"你们谁能处理
User类?"- Jackson 说:我可以转 JSON。
- XML Converter 说:我可以转 XML。
- 匹配客户端需求 :Spring 拿着服务端能提供的格式(JSON, XML)去跟客户端的
Accept头做交集。- 如果
Accept: application/xml,那就选中 XML Converter。 - 如果
Accept: */*(都要) 或者没传,通常默认选列表里的第一个(通常是 JSON)。
- 如果
- 最终面试 (调用
canWrite方法): "XML Converter,你确认你能把User对象写成application/xml吗?"
- 统计服务端能力 :Spring 先问一遍所有 Converter:"你们谁能处理
- 结论:协商一致的那个 Converter 胜出。
3. 发送请求 (Client 端写:RestTemplate)
场景 :Java 代码调用 restTemplate.postForObject(url, userObj, ...)。
-
输入条件:
- Java 源类型 :你传入的对象类型(如
User对象,或者MultiValueMap)。 - Media 类型 :
- 情况 A:你手动设置了
HttpHeaders的Content-Type。 - 情况 B:你没设置(默认情况)。
- 情况 A:你手动设置了
- Java 源类型 :你传入的对象类型(如
-
判断逻辑 :
Spring 遍历 Converter 列表,挨个问(调用
canWrite方法):-
如果传的是
User对象:StringConverter:它是 User 类,我不接。FormConverter:它不是 Map,我不接。JacksonConverter:是 Object,我可以接!而且我默认的 Content-Type 是application/json。- 结果:Jackson 胜出,发送 JSON。
-
如果传的是
MultiValueMap:JacksonConverter:虽然后面也能处理,但优先级通常较低。FormConverter:我就是专门处理 MultiValueMap 的! 我的默认 Content-Type 是application/x-www-form-urlencoded。- 结果:FormConverter 胜出,发送表单数据。
-
4. 接收响应 (Client 端读:RestTemplate)
场景:远程服务器回包了,Java 要把 Body 转成对象。
-
输入条件:
- Java 目标类型 :你
postForObject方法里传入的responseType(如User.class)。 - Media 类型 :远程服务器响应头里的
Content-Type(如application/json)。
- Java 目标类型 :你
-
判断逻辑 :
Spring 遍历 Converter 列表,挨个问(调用
canRead方法):"喂,收到了一个
application/json的包,要把他转成User类,谁能干?"StringConverter:你要的是 User 类,我只能转 String,我干不了。JacksonConverter:JSON 格式?User 类?正合我意,放着我来!
-
结论:Jackson 胜出,反序列化对象。
总结一张表
| 动作 | 核心依据 (Key Factor 1) | 辅助依据 (Key Factor 2) | 关键方法 |
|---|---|---|---|
Server 读请求 (@RequestBody) |
Request Header: Content-Type |
Controller 参数类型 | canRead() |
Server 写响应 (@ResponseBody) |
Request Header: Accept |
Controller 返回值类型 | canWrite() |
Client 发请求 (RestTemplate) |
Java 对象类型 (User/Map) | (可选) 手动设置的 Content-Type | canWrite() |
Client 收响应 (RestTemplate) |
Response Header: Content-Type |
期望的返回值类型 (Class<T>) |
canRead() |
一句话:
- 读(Read)的时候:看对方发过来的是什么格式(Content-Type),找能懂这个格式的 Converter。
- 写(Write)的时候:看我要发的是什么对象(Java Type),以及对方想要什么格式(Accept),找个能匹配的 Converter。