HttpMessageConverter
本章聚焦 Spring MVC 的消息转换层。DispatcherServlet 负责把请求分发到 Controller,而 Controller 方法接收的参数和返回的结果,需要在与 HTTP 请求体/响应体之间进行格式转换——这正是 HttpMessageConverter 的职责。理解消息转换机制,是掌握 RESTful API 开发的根基。
本章与全局的关系:前面章节讲解了请求如何被映射到 Controller 方法,本章讲解方法参数如何从请求体中反序列化、返回值如何序列化到响应体。后续章节将在此基础上展开拦截器、异常处理等内容。
定义与作用
HttpMessageConverter 是 Spring MVC 提供的消息转换器接口,定义了"Java 对象与 HTTP 消息体之间双向转换"的标准契约。它的核心职责有两项:
- 读取(Read):把 HTTP 请求体(InputStream)转换为 Controller 方法的参数对象(如
@RequestBody EmployeeDTO dto) - 写入(Write):把 Controller 方法的返回值(如
Employee对象)转换为 HTTP 响应体(OutputStream)
生活类比:机场安检的双向翻译官
想象飞翔科技的国际航班安检口:
- 读取:外国旅客拿着英文登机牌(JSON/XML 请求体),翻译官(HttpMessageConverter)把它翻译成中文内部工单(Java 对象),交给安检员(Controller 方法)处理
- 写入:安检员处理完后出具中文报告(Java 返回值),翻译官再把它翻译成英文登机牌(JSON/XML 响应体),交还给旅客
关键认知:Controller 方法只关心 Java 对象,完全不感知 HTTP 层面的字节流格式。格式转换的脏活累活,全部由 HttpMessageConverter 包办。
核心原理
消息转换器选择流程
当 DispatcherServlet 通过 HandlerAdapter 调用 Controller 方法时,如果方法参数标注了 @RequestBody 或返回值标注了 @ResponseBody,Spring MVC 会启动消息转换流程:
选择逻辑详解:
- 读取阶段:Spring 遍历所有已注册的
HttpMessageConverter,调用canRead(Class<?> clazz, MediaType mediaType)判断该转换器能否处理当前请求的Content-Type和目标类型 - 写入阶段:Spring 遍历所有转换器,调用
canWrite(Class<?> clazz, MediaType mediaType)判断能否把返回值序列化为客户端Accept头要求的格式 - 第一个匹配的转换器胜出,一旦找到就立即停止遍历
Content-Type 与转换器匹配过程
匹配规则:
| 请求头 | 含义 | 匹配的转换器 |
|---|---|---|
Content-Type: application/json | 请求体是 JSON 格式 | MappingJackson2HttpMessageConverter |
Content-Type: application/xml | 请求体是 XML 格式 | Jaxb2RootElementHttpMessageConverter |
Content-Type: application/x-www-form-urlencoded | 表单提交 | FormHttpMessageConverter |
Accept: text/plain | 期望纯文本响应 | StringHttpMessageConverter |
Accept: text/html | 期望 HTML 响应 | StringHttpMessageConverter |
适用位置与常用属性
HttpMessageConverter 本身是一个接口,开发者通常不直接实现它,而是通过配置影响其行为。以下是 Spring MVC 默认注册的主要转换器:
| 转换器类 | 支持的 MediaType | 用途 |
|---|---|---|
ByteArrayHttpMessageConverter | application/octet-stream, */* | 字节数组读写 |
StringHttpMessageConverter | text/plain, */* | 字符串读写,默认编码 ISO-8859-1 |
MappingJackson2HttpMessageConverter | application/json, application/*+json | JSON 与 Java 对象互转(最常用) |
Jaxb2RootElementHttpMessageConverter | application/xml, text/xml | XML 与 Java 对象互转 |
FormHttpMessageConverter | application/x-www-form-urlencoded | 表单数据读写 |
ResourceHttpMessageConverter | */* | Spring Resource 对象读写 |
配置属性(application.properties)
| 属性 | 作用 |
|---|---|
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss | 统一 JSON 日期格式 |
spring.jackson.time-zone=GMT+8 | 设置 JSON 序列化时区 |
spring.jackson.default-property-inclusion=non_null | 忽略 null 字段 |
spring.mvc.converters.preferred-json-mapper=jackson | 指定 JSON 库 |
完整示例
场景
飞翔科技员工管理系统需要对外提供 RESTful API。前端黄俪使用 Vue.js 开发,所有前后端交互通过 JSON 进行。后端小崔需要确保 Controller 能正确接收 JSON 请求体、返回 JSON 响应体。
项目结构
employee-web/
├── src/main/java/
│ └── com/feixiang/web/
│ ├── EmployeeController.java
│ ├── dto/
│ │ └── EmployeeDTO.java
│ └── config/
│ └── WebConfig.java
└── pom.xml
依赖(pom.xml)
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
Spring Boot 已自动引入,无需额外配置。
DTO 与 Controller
// EmployeeDTO.java
package com.feixiang.web.dto;
public class EmployeeDTO {
private String name;
private String department;
private Double salary;
// getters and setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public Double getSalary() { return salary; }
public void setSalary(Double salary) { this.salary = salary; }
}
// EmployeeController.java
package com.feixiang.web;
import com.feixiang.web.dto.EmployeeDTO;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
@PostMapping
public EmployeeDTO addEmployee(@RequestBody EmployeeDTO dto) {
// 模拟保存后返回(实际会调用 Service 层)
dto.setName(dto.getName() + "-已保存");
return dto;
}
@GetMapping("/{id}")
public EmployeeDTO getEmployee(@PathVariable Long id) {
EmployeeDTO dto = new EmployeeDTO();
dto.setName("张三");
dto.setDepartment("研发部");
dto.setSalary(15000.0);
return dto;
}
}
HTTP 请求示例 1:POST 提交 JSON
$ curl -X POST http://localhost:8080/api/employees \
-H "Content-Type: application/json" \
-d '{"name":"李四","department":"产品部","salary":12000}'
响应:
{
"name": "李四-已保存",
"department": "产品部",
"salary": 12000.0
}
流程解析:
- 请求头
Content-Type: application/json告诉 Spring:请求体是 JSON MappingJackson2HttpMessageConverter.canRead()返回 true(支持 JSON)- Jackson 把 JSON 字符串反序列化为
EmployeeDTO对象 - Controller 方法执行,修改 name 字段
- 返回值需要序列化,检查
Accept头(curl 默认*/*) MappingJackson2HttpMessageConverter.canWrite()返回 true- Jackson 把
EmployeeDTO序列化为 JSON 写入响应体
HTTP 请求示例 2:GET 返回 JSON
$ curl -X GET http://localhost:8080/api/employees/1 \
-H "Accept: application/json"
响应:
{
"name": "张三",
"department": "研发部",
"salary": 15000.0
}
HTTP 请求示例 3:Accept 不匹配导致 406
$ curl -X GET http://localhost:8080/api/employees/1 \
-H "Accept: application/pdf"
响应:
HTTP/1.1 406 Not Acceptable
原因:没有任何已注册的 HttpMessageConverter 能把 EmployeeDTO 序列化为 application/pdf 格式。
注册自定义转换器
场景:自定义 YAML 消息转换器
架构师白歌要求系统支持 YAML 格式的 API 交互。小崔需要注册一个自定义的 YamlHttpMessageConverter。
// YamlHttpMessageConverter.java
package com.feixiang.web.config;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class YamlHttpMessageConverter<T> extends AbstractHttpMessageConverter<T> {
private final Yaml yaml = new Yaml();
public YamlHttpMessageConverter() {
super(new MediaType("application", "yaml"));
}
@Override
protected boolean supports(Class clazz) {
return true; // 简化处理,实际应做类型判断
}
@Override
protected T readInternal(Class<? extends T> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return yaml.loadAs(new InputStreamReader(inputMessage.getBody(), StandardCharsets.UTF_8), clazz);
}
@Override
protected void writeInternal(T t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
yaml.dump(t, new OutputStreamWriter(outputMessage.getBody(), StandardCharsets.UTF_8));
}
}
// WebConfig.java
package com.feixiang.web.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// 添加到转换器链中
converters.add(new YamlHttpMessageConverter<>());
}
}
测试自定义转换器
$ curl -X POST http://localhost:8080/api/employees \
-H "Content-Type: application/yaml" \
-d 'name: 王五
department: 测试部
salary: 10000'
响应:
!!com.feixiang.web.dto.EmployeeDTO
name: "王五-已保存"
department: "测试部"
salary: 10000.0
易错场景与面试考点
误区一:@RequestBody 可以省略
错误代码:
@PostMapping
public EmployeeDTO addEmployee(EmployeeDTO dto) { // 缺少 @RequestBody
return dto;
}
错误现象:dto 的所有字段都是 null。
纠正:没有 @RequestBody 时,Spring 不会使用 HttpMessageConverter 读取请求体,而是尝试用 ServletModelAttributeMethodProcessor 从请求参数中绑定数据。对于 JSON 请求体,必须加 @RequestBody。
误区二:返回 String 时意外得到 JSON 引号
错误代码:
@GetMapping("/message")
public String getMessage() {
return "success";
}
意外响应:"success"(带双引号)
原因:如果方法在 @RestController 中,Spring 会把返回值交给 StringHttpMessageConverter 处理。但如果 Jackson 排在前面且 Accept: application/json,Jackson 会把字符串当作 JSON 字符串序列化,加上引号。
解决:确保 StringHttpMessageConverter 优先级高于 Jackson,或客户端指定 Accept: text/plain。
误区三:Content-Type 和 Accept 混为一谈
| 请求头 | 方向 | 作用 |
|---|---|---|
Content-Type | 请求 → 服务器 | "我发送的数据是什么格式" |
Accept | 请求 → 服务器 | "我希望你返回什么格式" |
面试高频:Content-Type 决定读取时用哪个转换器;Accept 决定写入时用哪个转换器。两者完全独立。
面试高频:Spring MVC 默认有哪些 HttpMessageConverter?
标准回答:Spring Boot 自动配置后,默认注册的主要有:
ByteArrayHttpMessageConverter— 字节数组StringHttpMessageConverter— 字符串(默认 ISO-8859-1,中文需配置 UTF-8)MappingJackson2HttpMessageConverter— JSON(最常用)Jaxb2RootElementHttpMessageConverter— XML(需 JAXB 依赖)FormHttpMessageConverter— 表单数据
小结
HttpMessageConverter 是 Spring MVC 的消息翻译层,负责 Java 对象与 HTTP 消息体之间的双向转换。@RequestBody 触发读取流程(请求体 → Java 对象),@ResponseBody 触发写入流程(Java 对象 → 响应体)。MappingJackson2HttpMessageConverter 是 RESTful 开发中最核心的转换器,负责 JSON 的序列化与反序列化。
本章与全局的关系:本章讲解了 Controller 方法参数和返回值如何与 HTTP 消息体交互。下一章"拦截器与跨域"将讲解请求在到达 Controller 之前和离开 Controller 之后,如何被拦截器拦截处理。