Skip to content

SpringMVC教程 - 11 消息转换器

在HTTP协议章节,我们学习了 请求协议响应协议,知道了请求体、响应体会有不同的格式,服务器接收到请求,会将不同格式的请求数据转换为 Java 对象,然后经过处理,最终再将 Java 对象转换为不同格式的数据返回给请求者。

在这个过程中,Spring MVC 为此提供了一套强大的 消息转换机制 —— HttpMessageConverter,用于在 HTTP 请求和响应体之间完成 Java 对象与数据格式的相互转换。

方向作用
读取请求数据把请求体的数据(如 JSON、表单数据)转换成 Java 对象
写出响应数据把 Java 对象转换成响应体(如 JSON、文本、图片等)返回给客户端

一句话总结:

消息转换器HttpMessageConverter负责: Request Body ⇋ Java Object ⇋ Response Body 的转换

常用的消息转换器有:

转换器负责内容(请求 / 响应)支持的 Content-Type描述
StringHttpMessageConverter文本 ⇋ 字符串text/plaintext/html将请求体中的文本转换为 String;返回值为 String 时,用它输出文本页面或纯文本。例如使用thymeleaf模板生成的HTML,最终通过该转换器转换为文本返回给浏览器。
FormHttpMessageConverter表单 —> MultiValueMap / 对象application/x-www-form-urlencoded通过表单提交数据到服务器时,可将表单键值对绑定为对象,需要使用 @RequestBody 来接收表单内容时才会用到。
MappingJackson2HttpMessageConverterJSON ⇋ 对象application/json需要依赖 Jackson;请求时负责将 JSON 转换为 Java 对象(反序列化),响应时将Java对象转换为JSON(序列化)。也就是序列化和反序列化的处理。
ByteArrayHttpMessageConverter二进制 ⇋ byte[]application/octet-stream主要用于文件下载 / 返回二进制内容,如下载文件、验证码图片
ResourceHttpMessageConverter文件资源 ⇋ Resource多种类型(如 image/pngapplication/octet-stream用于返回资源对象(FileSystemResourceUrlResource 等),多用于文件下载或图片显示

这些转换器都是实现或间接实现 HttpMessageConverter 接口,其中,前后端分离最常用的是 MappingJackson2HttpMessageConverter,用于 JSON 转换。

在前面我们使用 @RequestBody@ResponseBody 注解的时候,就已经在使用了,这里在回顾和理解一下。

11.1 HttpServletResponse

在前面 域对象 章节中介绍了 HttpServletRequest 对象,用于浏览器向 Controller 传递参数,而服务器向浏览器返回数据,需要使用 HttpServletResponse 对象,我们只需要向 response 对象写入数据,就可以返回数据了。

举个栗子:

创建一个

java
package com.foooor.hellospringmvc.controller;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import java.io.IOException;
import java.io.PrintWriter;

@Controller
public class HelloController {

    // ...接口

    /**
     * 测试HttpServletResponse
     * @param response
     */
    @GetMapping("/test-response")
    public void testResponse(HttpServletResponse response) {
        try {
            // 一定要在 getWriter() 之前设置,否则浏览器会乱码,同时设置text/html,让浏览器以html格式解析
            response.setContentType("text/html;charset=utf-8");

            PrintWriter writer = response.getWriter();
            writer.write("<html><body><b>For技术栈</b></body></html>");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 首先在 Controller 方法上,添加 HttpServletResponse 参数,就会自动注入 response 对象,这和 request 对象是一样的;
  • 然后就可以通过 response 对象来获取字节流(getOutputStream())或字符流(getWriter()),就可以写出数据了。

HttpServletResponse 的输出流(getWriter()getOutputStream())由 容器统一管理,请求结束后会自动 flush 和关闭,所以不需要手动关闭。但是如果你是读取文件的内容通过 response 写出,那么需要自己关闭读取文件的流就可以了。

测试:使用浏览器访问上面的接口,就可以在浏览器中看到加粗的 For技术栈 了。

通过上面的方式,是不会使用消息转换器来帮我们转换的,HttpServletRequest 是属于 Servlet API,是属于更底层的输出,而消息转换器是完成消息转换后,才调用 HttpServletResponse 输出数据的,所以 HttpServletResponse 是 SpringMVC 封装的上层工具。

11.2 @ResponseBody

在前面的章节已经使用了这个注解,它的作用是告诉 SpringMVC 将 Controller 的返回值通过合适的转换器 ,转换为字符串格式返回。

我们在 HelloWorld 的程序和编写 RESTful API 的时候,返回的是封装的 Result 对象,所以最终会通过 MappingJackson2HttpMessageConverter 转换器,将对象转换为 JSON 格式字符串返回。

java
@GetMapping("/")
@ResponseBody
public Result index() {
    return Result.success("Hello For技术栈!");
}
  • 当方法添加 @ResponseBody 注解,方法返回值是对象,会自动使用 MappingJackson2HttpMessageConverter 转换器将对象转换为 JSON 字符串响应给接口请求者。

如果 Controller 方法直接返回字符串呢?

java
@GetMapping("/hello")
@ResponseBody
public String hello() {
    return "Hello For技术栈!";
}
  • 首先因为方法添加了 @ResponseBody 注解,所以返回的结果不再是视图名称了!
  • 但此时也不会使用 MappingJackson2HttpMessageConverter 转换器了,而是使用 StringHttpMessageConverter 转换器,直接以字符串的形式返回。

但是返回的字符串中包含中文,浏览器显示的时候会出现乱码,因为 StringHttpMessageConverter 类中使用的是 ISO_8859_1 的编码,有点蛋疼。正常情况下我们一般也不会使用这个转换器(返回 JSON 的时候,使用的是UTF-8,没这个问题),如果实在想使用这个功能,可以在 SpringMVC 配置文件中,修改 <mvc:annotation-driven> 标签,添加如下配置:

xml
<mvc:annotation-driven>
    <mvc:message-converters register-defaults="true">
        <bean class="org.springframework.http.converter.StringHttpMessageConverter">
            <property name="defaultCharset" value="UTF-8"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>
  • <mvc:annotation-driven> 表示开启注解驱动,之前已经配置 ,现在需要修改一下其中的配置;
  • register-defaults="true" 是保留 Spring 默认的消息转换器(否则全都重写);
  • <mvc:message-converters> 自定义消息转换器区域,这里重新定义 StringHttpMessageConverter,修改字符编码。

11.3 @RequestBody

在前面 参数绑定 章节,已经介绍了 @RequestBody 注解的使用:

java
/**
 * 创建用户
 * POST /users
 */
@PostMapping
public Result createUser(@RequestBody UserDto userDto) {
    log.info("createUser: userDto={}", userDto);
    return Result.success("创建用户");
}

SpringMVC 会使用 MappingJackson2HttpMessageConverter 来解析 JSON 字符串,将 JSON 字符串转换为 Java 对象。但是需要注意,此时前端提交数据需要使用 JSON 格式。

11.4 RequestEntity

RequestEntity 是 Spring 提供的一个封装类,用来完整接收 HTTP 请求的请求头 + 请求体信息。也就是说其中封装了整个请求协议的信息。

举个栗子:

java
@PostMapping
@ResponseBody
public String addUser(RequestEntity<UserDto> entity) {
    // 获取请求头
    HttpHeaders headers = entity.getHeaders();
    // 获取请求体
    UserDto user = entity.getBody();
    // 获取请求方法
    HttpMethod method = entity.getMethod();
    // 获取请求URL
    URI url = entity.getUrl();
    // 获取Content-Type
    MediaType contentType = headers.getContentType();

    System.out.println("Headers: " + headers);
    System.out.println("Body: " + user);
    System.out.println("Method: " + method);
    System.out.println("URL: " + url);
    System.out.println("Content-Type: " + contentType);

    return "OK";
}
  • 使用 JSON 格式的 body 请求,会自动封装为 UserDto

只有在需要获取更详细的请求信息时,才需要用 RequestEntity,整体而言用的不多。

11.5 ResponseEntity

RequestEntity 相对,ResponseEntity 可以封装响应协议,包括:状态码、响应头、响应体。如果你有需求要定制相应协议,那么可以使用该类。

举个例子:

java
@GetMapping("/custom-header")
public ResponseEntity<UserDto> customHeader() {
    HttpHeaders headers = new HttpHeaders();
    // 添加自定义响应头
    headers.add("X-Powered-By", "Hello");

    UserDto userDto = new UserDto();
    userDto.setUsername("张三");
    userDto.setAge(18);
    userDto.setEmail("zhangsan@example.com");

    // 返回数据、响应头、状态码,返回的请求体会转换为 JSON 格式
    return new ResponseEntity<>(userDto, headers, HttpStatus.OK);
}
  • 请求体中的 userDto 对象,会被转换为 JSON 格式返回。

了解一下,用得不多,万一哪天用到,知道有这么个东西。

内容未完......