乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 SpringMVC概述与DispatcherServlet

    • 本章导读:Spring MVC概述与DispatcherServlet
    • Spring MVC 是什么
    • MVC 设计模式
    • 前端控制器模式
    • DispatcherServlet
    • 核心组件协作
  • 第2章 控制器与请求映射

    • 本章导读:控制器与请求映射
    • Controller
    • RestController
    • RequestMapping
    • GetMapping
    • PostMapping
    • PutMapping
    • DeleteMapping
    • PathVariable
    • RESTful
    • 请求映射原理
  • 第3章 请求参数获取与转换

    • 本章导读:请求参数获取与转换
    • RequestParam
    • RequestBody
    • RequestHeader
    • CookieValue
    • Model
    • ModelAttribute
    • 数据绑定原理
    • 数据校验
  • 第4章 响应数据与视图解析

    • 本章导读:响应数据与视图解析
    • ResponseBody
    • ResponseEntity
    • ModelAndView
    • ViewResolver
    • HttpMessageConverter
    • forward与redirect
  • 第5章 拦截器过滤器与跨域

    • 本章导读:拦截器、过滤器与跨域
    • HandlerInterceptor
    • WebMvcConfigurer
    • CrossOrigin
    • 登录验证实战
  • 第6章 文件上传与异常处理

    • 本章导读:文件上传与异常处理
    • MultipartFile
    • 文件下载
    • ExceptionHandler
    • ControllerAdvice
    • RestControllerAdvice
    • ResponseStatus
  • 第7章 高级特性与最佳实践

    • 本章导读:高级特性与最佳实践
    • SessionAttributes
    • SessionAttribute
    • RedirectAttributes
    • MockMvc测试
    • 国际化
    • 最佳实践
  • 第8章 扩展与异步机制

    • 本章导读:扩展与异步机制
    • 异步请求处理
    • 自定义参数解析器
    • 内容协商

HttpMessageConverter

本章聚焦 Spring MVC 的消息转换层。DispatcherServlet 负责把请求分发到 Controller,而 Controller 方法接收的参数和返回的结果,需要在与 HTTP 请求体/响应体之间进行格式转换——这正是 HttpMessageConverter 的职责。理解消息转换机制,是掌握 RESTful API 开发的根基。

本章与全局的关系:前面章节讲解了请求如何被映射到 Controller 方法,本章讲解方法参数如何从请求体中反序列化、返回值如何序列化到响应体。后续章节将在此基础上展开拦截器、异常处理等内容。


定义与作用

HttpMessageConverter 是 Spring MVC 提供的消息转换器接口,定义了"Java 对象与 HTTP 消息体之间双向转换"的标准契约。它的核心职责有两项:

  1. 读取(Read):把 HTTP 请求体(InputStream)转换为 Controller 方法的参数对象(如 @RequestBody EmployeeDTO dto)
  2. 写入(Write):把 Controller 方法的返回值(如 Employee 对象)转换为 HTTP 响应体(OutputStream)

生活类比:机场安检的双向翻译官

想象飞翔科技的国际航班安检口:

  • 读取:外国旅客拿着英文登机牌(JSON/XML 请求体),翻译官(HttpMessageConverter)把它翻译成中文内部工单(Java 对象),交给安检员(Controller 方法)处理
  • 写入:安检员处理完后出具中文报告(Java 返回值),翻译官再把它翻译成英文登机牌(JSON/XML 响应体),交还给旅客

关键认知:Controller 方法只关心 Java 对象,完全不感知 HTTP 层面的字节流格式。格式转换的脏活累活,全部由 HttpMessageConverter 包办。


核心原理

消息转换器选择流程

当 DispatcherServlet 通过 HandlerAdapter 调用 Controller 方法时,如果方法参数标注了 @RequestBody 或返回值标注了 @ResponseBody,Spring MVC 会启动消息转换流程:

选择逻辑详解:

  1. 读取阶段:Spring 遍历所有已注册的 HttpMessageConverter,调用 canRead(Class<?> clazz, MediaType mediaType) 判断该转换器能否处理当前请求的 Content-Type 和目标类型
  2. 写入阶段:Spring 遍历所有转换器,调用 canWrite(Class<?> clazz, MediaType mediaType) 判断能否把返回值序列化为客户端 Accept 头要求的格式
  3. 第一个匹配的转换器胜出,一旦找到就立即停止遍历

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用途
ByteArrayHttpMessageConverterapplication/octet-stream, */*字节数组读写
StringHttpMessageConvertertext/plain, */*字符串读写,默认编码 ISO-8859-1
MappingJackson2HttpMessageConverterapplication/json, application/*+jsonJSON 与 Java 对象互转(最常用)
Jaxb2RootElementHttpMessageConverterapplication/xml, text/xmlXML 与 Java 对象互转
FormHttpMessageConverterapplication/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
}

流程解析:

  1. 请求头 Content-Type: application/json 告诉 Spring:请求体是 JSON
  2. MappingJackson2HttpMessageConverter.canRead() 返回 true(支持 JSON)
  3. Jackson 把 JSON 字符串反序列化为 EmployeeDTO 对象
  4. Controller 方法执行,修改 name 字段
  5. 返回值需要序列化,检查 Accept 头(curl 默认 */*)
  6. MappingJackson2HttpMessageConverter.canWrite() 返回 true
  7. 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 自动配置后,默认注册的主要有:

  1. ByteArrayHttpMessageConverter — 字节数组
  2. StringHttpMessageConverter — 字符串(默认 ISO-8859-1,中文需配置 UTF-8)
  3. MappingJackson2HttpMessageConverter — JSON(最常用)
  4. Jaxb2RootElementHttpMessageConverter — XML(需 JAXB 依赖)
  5. FormHttpMessageConverter — 表单数据

小结

HttpMessageConverter 是 Spring MVC 的消息翻译层,负责 Java 对象与 HTTP 消息体之间的双向转换。@RequestBody 触发读取流程(请求体 → Java 对象),@ResponseBody 触发写入流程(Java 对象 → 响应体)。MappingJackson2HttpMessageConverter 是 RESTful 开发中最核心的转换器,负责 JSON 的序列化与反序列化。

本章与全局的关系:本章讲解了 Controller 方法参数和返回值如何与 HTTP 消息体交互。下一章"拦截器与跨域"将讲解请求在到达 Controller 之前和离开 Controller 之后,如何被拦截器拦截处理。

上一页
ViewResolver
下一页
forward与redirect