从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机相关技巧
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • 最全面的 RSS 教程
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金
  • 职场规划
  • 关于离职
  • 杂谈
  • 自媒体
  • 📖 读书

    • 读书工具
    • 走进科学
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
    • 读书笔记
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

    • SpringBoot 教程-尚硅谷

      • SpringBoot 课程介绍
      • Spring 和 SpringBoot
      • HelloWorld
      • 了解自动配置原理
      • 底层注解-@Configuration 详解
      • 底层注解-@Import 导入组件
      • 底层注解-@Conditional 条件装配
      • 原生配置文件引入-@ImportResource
      • 底层注解-配置绑定 @ConfigurationProperties
      • 自动配置原理
      • 自动配置流程
      • Lombok 简化开发
      • DevTools
      • Spring-Initailizr
      • 配置文件-Yaml 用法
      • Web 开发简介
      • web 开发-静态资源规则于定制化
      • 静态资源配置原理
      • Rest 映射及源码解析
      • 请求映射原理
      • 常用参数注解使用
      • MatrixVariable:矩阵变量
      • 各种类型参数解析原理
      • Servlet-API 参数解析原理
      • Model、Map 参数解析原理
      • 自定义对象参数绑定原理
      • 自定义 Converter 原理
      • 数据响应原理
        • 依赖分析
        • 新增 controller
        • 返回值解析器
        • handleReturnValue
        • 总结
      • 内容协商原理
      • 基于请求参数的内容原理
      • 自定义 MessageConverter 原理
      • Thymeleaf 初体验
      • web 实验-后台管理系统
      • web 实验-抽取公共页面
      • web 实验-遍历数据
      • 源码分析-视图解析器与视图
      • 拦截器-登录检查与静态资源放行
      • 拦截器的执行时机和原理
      • 单文件和多文件上传的使用
      • 文件上传原理
      • 错误处理机制
      • 错误处理-底层组件源码分析
      • 异常处理流程
      • 几种异常处理原理
      • Web 原生对象注入
      • 嵌入式 Servlet 容器
      • 定制化原理
      • 数据库场景的自动配置分析和整合测试
      • 自定义方式整合 Druid
      • 通过 starter 整合 Druid
      • 整合 Mybatis
      • 使用注解整合 Mybatis
      • 整合 MybatisPlus 操作数据库
      • MybatisPlus-列表分页展示
      • 整合 Redis
      • 单元测试-Junit5
      • 单元测试-断言机制
      • 单元测试-前置条件
      • 单元测试-嵌套测试
      • 单元测试-参数化测试
      • 指标监控-基本概念
      • 指标监控-配置 EndPoint
      • 指标监控-可视化
      • 原理解析-Profile 功能
      • 配置文件深入
      • 自定义 Starter
      • SpringApplication 初始化过程
      • SpringBoot 完整启动过程
  • Java
  • SpringBoot
  • SpringBoot 教程-尚硅谷
2023-08-22
目录

数据响应原理

# 280.数据响应原理

之前我们说了 SpringMVC 的参数处理原理,接下来就说说数据响应的原理。

首先,数据响应分为两个模块:

  • 响应页面:这个我们在后面的视图解析中详细解释,一般是一个规模比较小的系统中使用;
  • 响应数据:JSON,XML,Excel,图片,音视频,自定义协议数据,一般是前后端分离时使用;

# 依赖分析

之所以能返回 JSON 数据,是因为我们引入了 web 开发的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
1
2
3
4

而该依赖又引入了 JSON 的依赖:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
  <version>2.3.4.RELEASE</version>
  <scope>compile</scope>
</dependency>
1
2
3
4
5
6

‍ 最后,底层用的 Jackson 依赖:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jdk8</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId>
  <version>2.11.2</version>
  <scope>compile</scope>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

‍ 接下来我们才尝试下,返回一个 JSON 数据。

# 新增 controller

新增一个 ResponseTestController 类,用来测试。我们只需在方法上加一个@ResponseBody,即可完成 JSON 数据的返回:

package com.peterjxl.boot.controller;


import com.peterjxl.boot.bean.Person;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ResponseTestController {

    @ResponseBody
    @GetMapping("/test/person")
    public Person getPerson(){
        Person person = new Person();
        person.setUserName("peterjxl");
        person.setAge(20);
        return person;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

‍ 测试:我们访问 localhost: 8888/test/person (opens new window),可以看到能正常看到 JSON 数据

‍

# 返回值解析器

接下来我们以 debug 的方式启动,然后重新访问;

然后一步步 debug,直到 RequestMappingHandlerAdapter 适配器这里,可以看到有一个 returnValueHandlers,这个叫做返回值解析器:

‍

在我们的方法,return 返回值之前,SpringBoot 已经帮我们设置好了返回值解析器,就等方法执行完,有了返回值之后处理。

这里扩展一下,SpringMVC 支持什么类型的返回值:

ModelAndView
Model
View
ResponseEntity 
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 注解的且为对象类型的
有 @ResponseBody 注解的,对应的handler为 ---> RequestResponseBodyMethodProcessor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

‍ 继续 debug,直到 ServletInvocableHandlerMethod 类的 invokeAndHandle 方法:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,			Object... providedArgs) throws Exception {

	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	setResponseStatus(webRequest);

	if (returnValue == null) {
		if (isRequestNotModified(webRequest) || getResponseStatus() != null 
                                                     || mavContainer.isRequestHandled()) {
			disableContentCachingIfNecessary(webRequest);
			mavContainer.setRequestHandled(true);
			return;
		}
	}
	else if (StringUtils.hasText(getResponseStatusReason())) {
		mavContainer.setRequestHandled(true);
		return;
	}

	mavContainer.setRequestHandled(false);
	Assert.state(this.returnValueHandlers != null, "No return value handlers");
	try {
		this.returnValueHandlers.handleReturnValue(
			returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		if (logger.isTraceEnabled()) {
			logger.trace(formatErrorForReturnValue(returnValue), ex);
		}
		throw ex;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

‍ 该方法首先会去执行处理器的方法,然后得到返回值:

‍

后面的两个 if 判断,则是判断是否返回值为 null,并判断返回值是否有错误信息;

if (returnValue == null) {
    if (isRequestNotModified(webRequest) || getResponseStatus() != null  || mavContainer.isRequestHandled()) {
	disableContentCachingIfNecessary(webRequest);
	mavContainer.setRequestHandled(true);
	return;
    }
}
else if (StringUtils.hasText(getResponseStatusReason())) {
    mavContainer.setRequestHandled(true);
    return;
}
1
2
3
4
5
6
7
8
9
10
11

‍ 接下来就是调用 returnValueHandlers.handleReturnValue 方法,来处理返回值(第 4 行):

mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
	this.returnValueHandlers.handleReturnValue(
			returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
	if (logger.isTraceEnabled()) {
		logger.trace(formatErrorForReturnValue(returnValue), ex);
	}
	throw ex;
}
1
2
3
4
5
6
7
8
9
10
11
12

‍ 我们步入进去:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
1
2
3
4
5
6
7
8
9

代码解读:

  • 第 4 行:首先根据返回值(returnValue)和返回类型(returnType),寻找要用什么返回值处理器(handler)
  • 第 5 行:判断是否找到了处理器,没有则抛出异常
  • 第 8 行:处理返回值 ‍ 接下来我们步入 selectHandler 方法:
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
	boolean isAsyncValue = isAsyncReturnValue(value, returnType);
	for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
		if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
			continue;
		}
		if (handler.supportsReturnType(returnType)) {
			return handler;
		}
	}
	return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

代码解读:

  • 首先调用判断 isAsyncReturnValue 方法判断是否一个异步返回值,这里直接说结论,是返回 false;该方法就在 HandlerMethodReturnValueHandler 方法的下方,里面是一个 for 循环,逐个遍历 handler。
  • 然后 for 循环遍历所有 handler,判断是否支持当前参数,是则返回

HandlerMethodReturnValueHandler 其实是一个接口,里面有 2 个方法:

public interface HandlerMethodReturnValueHandler {
    boolean supportsReturnType(MethodParameter returnType);

    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}
1
2
3
4
5

第一个方法是判断是否支持当前类型的返回值,第二个方法就是处理返回值 ‍ 那么具体怎么判断是否支持呢?很简单,我们 debug 看看源码即可。首先第一个 handler 是 ModelAndViewMethodReturnValueHandler,其 supportsReturnType 方法:

@Override
public boolean supportsReturnType(MethodParameter returnType) {
	return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}
1
2
3
4

也就是看返回值类型,是否 ModelAndView;这里不是,因此返回 false。

继续回到我们的 for 循环,我们一步步放行,知道找到对应的 handler:RequestResponseBodyMethodProcessor

‍

其 supportsReturnType 方法源码如下:就是判断是否有 @ResponseBody 注解

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class));
}
1
2
3
4

# handleReturnValue

有了 handler,接下来就是看如何处理返回值了,我们 debug 出来,并步入到 handleReturnValue 方法中:

‍

其源码如下:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

    mavContainer.setRequestHandled(true);
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);

    // Try even with null return value. ResponseBodyAdvice could get involved.
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
1
2
3
4
5
6
7
8
9
10

‍ 前几行代码都是初始化一些数据,然后调用 writeWithMessageConverters 方法:

‍

接下来就涉及到一个新的数据类型:MediaType,也叫媒体类型。

MediaType selectedMediaType = null;
1

‍ 说到媒体类型,就得提到内容协商。我们在发送 HTTP 请求的时候,其实浏览器会在请求头上,加上 Accept,表明支持什么类型的数据:

说明:

  1. 根据内容可知,支持 HTML,XML,image 等类型
  2. */* 表明支持所有数据类型
  3. q 表示是权重,数值越大,权重越大。这里的含义是,浏览器优先支持 HTML,xml 等字符类型;然后也支持 image 等图片类型

那么服务器,告诉浏览器返回的数据类型,就是内容协商。这里仅仅做个简单的介绍,下一篇博客再详细说 ‍ 接下来就是先判断,是否已经有媒体类型了,因为之前也有处理请求,有可能已经设置了响应类型。如果有则直接返回媒体类型:

MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();
if (isContentTypePreset) {
	if (logger.isDebugEnabled()) {
		logger.debug("Found 'Content-Type:" + contentType + "' in response");
	}
	selectedMediaType = contentType;
}

1
2
3
4
5
6
7
8
9

‍ 如果没有设置返回类型,则先获取支持的类型(acceptableTypes),还有能返回的数据类型(producibleTypes)

else {
    HttpServletRequest request = inputMessage.getServletRequest();
    List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
    List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);
    //....
1
2
3
4
5

‍ 通过 debug 可以发现,支持的数据类型就是浏览器传过来的数据类型:

‍

而返回的数据类型则是 JSON:

‍

然后就是判断,浏览器支持的数据中,是否有服务器能返回的数据类型(双重 for 循环):

List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
	for (MediaType producibleType : producibleTypes) {
		if (requestedType.isCompatibleWith(producibleType)) {
			mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
		}
	}
}
1
2
3
4
5
6
7
8

最后在 mediaTypesToUse 集合中,存放能返回给浏览器的数据类型,这就是内容协商的过程,下一篇博客再详细解释(也就是 for 循环里的内容)

然后关键的来了,既然返回给浏览器的数据类型有了,接下来就是找哪个 messageConverter,能够将数据转换为 JSON。

ps:也是通过 for 循环的方式,在 SpringBoot 中有非常多这样的写法

if (selectedMediaType != null) {
    selectedMediaType = selectedMediaType.removeQualityValue();
    for (HttpMessageConverter<?> converter : this.messageConverters) {
	// 判断是否支持....
    }
}
1
2
3
4
5
6

那么 MessageConverter 具体是怎么做的呢?首先,这是一个接口,有如下方法:

public interface HttpMessageConverter<T> {
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    List<MediaType> getSupportedMediaTypes();
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage) 
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) 
}
1
2
3
4
5
6
7

说明:

  • canRead:判断是否支持读取当前的数据类型
  • canWrite:能否将数据类型转换出去
  • read:读取数据
  • write:转换数据
  • getSupportedMediaTypes:返回支持的数据类型 ‍ 总的来说,就是看是否支持将 Class 类型的对象,转为 MediaType 类型的数据。

例子:Person 对象转为 JSON(转对象为 JSON 返回给浏览器),或者 JSON 转为 Person(读取 JSON 转为对象)

SpringBoot 自带的 messageConverter 如下:

这里也说下各个 converter 支持转换的数据类型:

0 - 只支持 Byte 类型的

1 - String

2 - String

3 - Resource

4 - ResourceRegion

5 - DOMSource.class \ SAXSource.class) \ StAXSource.class \StreamSource.class \Source.class

6 - MultiValueMap

7 - true

8 - true

9 - 支持注解方式处理 XML 的类型

然后回到我们的代码,for 循环中就会逐个遍历 converter,并调用 canWrite 方法来判断是否支持转换,以第一个 converter,ByteArrayHttpMessageConverter 为例,其判断是否支持的方法如下

@Override
public boolean supports(Class<?> clazz) {
	return byte[].class == clazz;
}
1
2
3
4

可以看到很简单,就是判断其数据类型是否为 byte,是则返回 true。 ‍ 然后我们 for 循环会执行到第 7 个 converter,也就是 Jackson 类型的转换器,转换数据并写入到 response 对象中,至此,响应数据完成。 ‍ 我们来看看其源码:首先会加一些判断,然后调用 write 方法,我们步入进去:

然后进去到 write 方法内部:

‍

我们步入,然后就是 Jackson 内部转换 JSON 的代码了,这里不表。

接下来就是写入数据了,可以看到写入后确实是 JSON

# 总结

数据响应的步骤:

  1. 返回值处理器判断是否支持这种类型返回值 supportsReturnType

  2. 返回值处理器调用 handleReturnValue 进行处理

  3. RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。利用 MessageConverters 进行处理,将数据写为 JSON

  4. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)

  5. 服务器最终根据自身的能力,决定服务器能生产出什么样内容类型的数据

  6. SpringMVC 会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理当前的返回数据练习

    • 得到 MappingJackson2HttpMessageConverter 可以将对象写为 JSON
    • 利用 MappingJackson2HttpMessageConverter 将对象转为 JSON 再写出去。 ‍ 已将本文源码上传到 Gitee (opens new window) 或 GitHub (opens new window) 的分支 demo16,读者可以通过切换分支来查看本文的示例代码
上次更新: 2025/6/3 09:31:54
自定义 Converter 原理
内容协商原理

← 自定义 Converter 原理 内容协商原理→

最近更新
01
语雀文档一键下载至本地教程
07-04
02
要成功,就不要低估环境对你的影响
07-03
03
血泪教训:电子设备要定期开机
07-02
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式