数据响应原理
# 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,读者可以通过切换分支来查看本文的示例代码