SpringBoot源码系列(7):返回值处理器

布鸽不鸽 Lv4

前言

在SpringBoot/SpringMVC中,我们在@ResponseBody注解标注的Controller中直接返回对象,即可为请求返回所需类型的返回值(比如json格式)。本文就来谈谈返回值处理器的原理。

原文地址:https://xuedongyun.cn/post/44248/

返回值处理器原理

RequestMappingHandlerAdapter

有关参数解析的原理,可以查看我之前的文章:SpringBoot源码系列(5):参数解析

RequestMappingHandlerAdapter执行了handleInternal方法。代码中,主要设置了请求值解析器和返回值解析器,然后调用了invocableMethod.invokeAndHandle进行处理

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
// 代码有删改
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

// argumentResolvers:请求值解析器,用于确定目标方法每一个参数值是什么
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// returnValueHandlers:返回值处理器
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}

ModelAndViewContainer mavContainer = new ModelAndViewContainer();

// 执行并处理
invocableMethod.invokeAndHandle(webRequest, mavContainer);

return getModelAndView(mavContainer, modelFactory, webRequest);
}
}

ServletInvocableHandlerMethod

RequestMappingHandlerAdapter中,调用了invocableMethod.invokeAndHandle。我们现在查看ServletInvocableHandlerMethodinvokeAndHandle方法

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
// 代码有删改
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;
}

try {
// 利用返回值处理器,处理返回值
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
}

HandlerMethodReturnValueHandlerComposite

我们来看this.returnValueHandlershandleReturnValue方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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("...");
}

// 利用找到的返回值处理器进行处理
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}

其中,selectHandler方法选择合适的返回值处理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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;
}

this.returnValueHandlers一共有15种

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
this.returnValueHandlers = {ArrayList} size = 15
0 = {ModelAndViewMethodReturnValueHandler}
1 = {ModelMethodProcessor}
2 = {ViewMethodReturnValueHandler}
3 = {ResponseBodyEmitterReturnValueHandler}
4 = {StreamingResponseBodyReturnValueHandler}
5 = {HttpEntityMethodProcessor}
6 = {HttpHeadersReturnValueHandler}
7 = {CallableMethodReturnValueHandler)
8 = {DeferredResultMethodReturnValueHandler}
9 = {AsyncTaskMethodReturnValueHandler}
10 = {ServletModelAttributeMethodProcessor}
11 = (RequestResponseBodyMethodProcessor}
12 = {ViewNameMethodReturnValueHandler}
13 = {MapMethodProcessor)
14 = {ServletModelAttributeMethodProcessor}

(例子一)ModelAndViewMethodReturnValueHandlersupportsReturnType方法:判断返回值类型是否继承自ModelAndView(也即返回值能否转为ModelAndView类型)

(例子二)RequestResponseBodyMethodProcessorsupportsReturnType方法:判断类或者方法,有没有加@ResponseBody注解

HandlerMethodReturnValueHandler

RequestResponseBodyMethodProcessor为例,查看handler.handleReturnValue方法

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 IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

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

// 消息转换器,进行写出操作,将返回值转换为json
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); // 步入
}

查看writeWithMessageConverters方法。这里包含“内容协商”的核心原理:浏览器能接收的类型“并上”服务器能提供的类型,使用第一个作为使用的媒体类型。之后第二次遍历messageConverters,寻找支持的Converter

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 代码有删改
protected <T> void writeWithMessageConverters(@Nullable T value,
MethodParameter returnType,
ServletServerHttpRequest inputMessage,
ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

Object body;
Class<?> valueType;
Type targetType;

// 判断返回值是不是字符串类型
if (value instanceof CharSequence) {
body = value.toString();
valueType = String.class;
targetType = String.class;
}
else {
body = value;
valueType = getReturnValueType(body, returnType);
targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
}

// 判断返回值是不是资源类型(是不是流数据)
if (isResourceType(value, returnType)) {
// 调用对流数据的处理方法,响应回去
}

// MediaType(媒体类型),牵扯到内容协商
// 浏览器发请求的时候,在请求头的Accept中告诉服务器能接收的类型
MediaType selectedMediaType = null;

// 先看看响应头中,有没有已经写好的响应内容类型
MediaType contentType = outputMessage.getHeaders().getContentType();
boolean isContentTypePreset = contentType != null && contentType.isConcrete();

// 如果response已经有了写好的响应类型,就直接用
if (isContentTypePreset) {
...
selectedMediaType = contentType;
}
// 不然,拿到能接收的媒体类型
else {
HttpServletRequest request = inputMessage.getServletRequest();
// 浏览器能接收的类型
List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
// 服务器能产生的类型(遍历所有MessageConverter,看哪些支持,进而统计媒体类型)
List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

// 计算最终能使用的媒体类型
List<MediaType> mediaTypesToUse = new ArrayList<>();
for (MediaType requestedType : acceptableTypes) {
for (MediaType producibleType : producibleTypes) {
if (requestedType.isCompatibleWith(producibleType)) {
mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
}
}
}

MediaType.sortBySpecificityAndQuality(mediaTypesToUse);

// 最终拿到selectedMediaType,比如这里就是"application/json"
for (MediaType mediaType : mediaTypesToUse) {
if (mediaType.isConcrete()) {
selectedMediaType = mediaType;
break;
}
else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) {
selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
break;
}
}
}


if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
// 遍历所有的HttpMessageConverter(消息转换器),看谁能处理。(可以看看这个接口的方法)
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter =
(converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ?
// GenericHttpMessageConverter由canWrite判断能否使用
((GenericHttpMessageConverter) converter).canWrite(targetType,
valueType,
selectedMediaType) :
// 这里AbstractHttpMessageConverter的canWrite实际上由support和canWrite组成
converter.canWrite(valueType, selectedMediaType)) {
body = getAdvice().
beforeBodyWrite(body,
returnType,
selectedMediaType,
(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
inputMessage, outputMessage);
if (body != null) {
Object theBody = body;
LogFormatUtils.traceDebug("...");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
// 用当前转换器,写出去,步入
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
}
return;
}
}
}
}

this.messageConverters有9个,能否使用的判断由supportcanWrite共同决定,下面只考虑了support的部分

1
2
3
4
5
6
7
8
9
10
11
this.messageConverters = {ArrayList} size = 9
0={ByteArrayHttpMessageConverter} 只支持Byte类型
1={StringHttpMessageConverter} 只支持String类型
2={StringHttpMessageConverter} 只支持String类型(这两个默认字符集不一样,UTF-8和ISO-8859-1)
3={ResourceHttpMessageConverter) 只支持返回值为Resource
4={ResourceRegionHttpMessageConverter} 只支持返回值为ResourceRegion
5={SourceHttpMessageConverter} 只支持返回值为DOMSource,SAXSource,
StAXSource,StreamSource,Source
6={AllEncompassingFormHttpMessageConverter} 只支持返回值为MultiValueMap
7={MappingJackson2HttpMessageConverter} 直接返回true
8={MappingJackson2HttpMessageConverter} 直接返回true

MappingJackson2HttpMessageConverter

genericConverter.write方法(此处为MappingJackson2HttpMessageConverter),利用底层的jackson包,ObjectMapper转换的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public final void write(final T t, @Nullable final Type type, @Nullable MediaType contentType,
HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {

final HttpHeaders headers = outputMessage.getHeaders();
// 向头中写入数据,此处为Content-Type=application/json
addDefaultHeaders(headers, t, contentType);

...

// 可以步入看看,jackson将对象转json,放入response中的一个outputBuffer中
writeInternal(t, type, outputMessage);
// 将Buffer中的内容写出
outputMessage.getBody().flush();
}

自定义MessageConverter

假设我们想自定义一个application/my-type协议,请求将返回的User以name-age的格式返回

我们首先要写一个类,实现HttpMessageConverter,重写canWritewritegetSupportedMediaTypes方法

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
32
33
34
public class MyConverter implements HttpMessageConverter<User> {

@Override
public boolean canRead(Class<?> clazz, MediaType mediaType) {
return false;
}

@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return clazz.isAssignableFrom(User.class);
}

@Override
public List<MediaType> getSupportedMediaTypes() {
return MediaType.parseMediaTypes("application/my-type");
}

@Override
public User read(Class<? extends User> clazz,
HttpInputMessage inputMessage)
throws HttpMessageNotReadableException {
return null;
}

@Override
public void write(User user,
MediaType contentType,
HttpOutputMessage outputMessage)
throws HttpMessageNotWritableException, IOException {
String data = user.getUserName() + "-" + user.getUserAge();
OutputStream body = outputMessage.getBody();
body.write(data.getBytes());
}
}

利用WebMvcConfigurer中的extendMessageConverters方法,我们可以向容器中添加自定义的converter

1
2
3
4
5
6
7
8
9
@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MyConverter myConverter = new MyConverter();
converters.add(myConverter);
}
}

最后,在我们前端的请求中,我们只需要在请求头指定:Accept=application,即可拿到我们想要的格式

总结和补充

  1. 标注了@ResponseBody注解
  2. 调用RequestResponseBodyMethodProcessor这个返回值处理器
  3. 调用各种MessageConverter进行数据的转换
  4. MessageConverter可以支持各种媒体类型数据的操作(读,写)
  5. 内容协商找到合适的MessageConverter

那原本已有的MessageConverter是如何被添加的呢?我们来简单看一看源码:

WebMvcAutoConfiguration中添加了所有的默认MessageConverter

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider.ifAvailable(
(customConverters) -> converters.addAll(customConverters.getConverters())
);
}
}

继续查看,简单列出一下执行过的函数:

customConverters.getConverters()->getDefaultConverters()->defaultMessageConverters()->getMessageConverters()->addDefaultHttpMessageConverters()

这里自动为我们添加了很多converter

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
if (!shouldIgnoreXml) {
try {
messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
}
messageConverters.add(new AllEncompassingFormHttpMessageConverter());

if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}

if (!shouldIgnoreXml) {
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
}

if (kotlinSerializationJsonPresent) {
messageConverters.add(new KotlinSerializationJsonHttpMessageConverter());
}
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
messageConverters.add(new JsonbHttpMessageConverter());
}

if (jackson2SmilePresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
}
if (jackson2CborPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
}
}

比如其中jackson2Present就是如此判断。这也就是为什么我们导入jackson-xml,就能使用的原因

1
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
  • 标题: SpringBoot源码系列(7):返回值处理器
  • 作者: 布鸽不鸽
  • 创建于 : 2023-05-18 16:51:23
  • 更新于 : 2023-06-26 09:30:50
  • 链接: https://xuedongyun.cn//post/5854/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论