数据响应原理
# 280.数据响应原理
之前我们说了 SpringMVC 的参数处理原理,接下来就说说数据响应的原理。
首先,数据响应分为两个模块:
- 响应页面:这个我们在后面的视图解析中详细解释,一般是一个规模比较小的系统中使用;
- 响应数据:JSON,XML,Excel,图片,音视频,自定义协议数据,一般是前后端分离时使用;
# 依赖分析
之所以能返回 JSON 数据,是因为我们引入了 web 开发的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
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>
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>
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;
}
}
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
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;
}
}
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;
}
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;
}
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);
}
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;
}
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;
}
2
3
4
5
第一个方法是判断是否支持当前类型的返回值,第二个方法就是处理返回值
那么具体怎么判断是否支持呢?很简单,我们 debug 看看源码即可。首先第一个 handler 是 ModelAndViewMethodReturnValueHandler
,其 supportsReturnType
方法:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}
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));
}
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);
}
2
3
4
5
6
7
8
9
10
前几行代码都是初始化一些数据,然后调用 writeWithMessageConverters
方法:
接下来就涉及到一个新的数据类型:MediaType,也叫媒体类型。
MediaType selectedMediaType = null;
说到媒体类型,就得提到内容协商。我们在发送 HTTP 请求的时候,其实浏览器会在请求头上,加上 Accept,表明支持什么类型的数据:
说明:
- 根据内容可知,支持 HTML,XML,image 等类型
*/*
表明支持所有数据类型- 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;
}
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);
//....
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));
}
}
}
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) {
// 判断是否支持....
}
}
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)
}
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;
}
2
3
4
可以看到很简单,就是判断其数据类型是否为 byte,是则返回 true。
然后我们 for 循环会执行到第 7 个 converter,也就是 Jackson 类型的转换器,转换数据并写入到 response 对象中,至此,响应数据完成。
我们来看看其源码:首先会加一些判断,然后调用 write
方法,我们步入进去:
然后进去到 write 方法内部:
我们步入,然后就是 Jackson 内部转换 JSON 的代码了,这里不表。
接下来就是写入数据了,可以看到写入后确实是 JSON
# 总结
数据响应的步骤:
返回值处理器判断是否支持这种类型返回值 supportsReturnType
返回值处理器调用 handleReturnValue 进行处理
RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。利用 MessageConverters 进行处理,将数据写为 JSON
内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
服务器最终根据自身的能力,决定服务器能生产出什么样内容类型的数据
SpringMVC 会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理当前的返回数据练习
- 得到 MappingJackson2HttpMessageConverter 可以将对象写为 JSON
- 利用 MappingJackson2HttpMessageConverter 将对象转为 JSON 再写出去。
已将本文源码上传到 Gitee (opens new window) 或 GitHub (opens new window) 的分支 demo16,读者可以通过切换分支来查看本文的示例代码