思考,输出,沉淀。用通俗的语言陈述技术,让自己和他人都有所收获。
作者:毅航😜
前言
对于SpringMVC
而言,@ReqesutBody
注解可以说是在日常开发中使用频率最高的一个注解了。但你有了解过SpringMVC
内部究竟是如何来解析@ReqesutBody
注解吗?不了解也没关系,接下来我们就重点对SpringMVC
中@RequestBody
的解析原理进行深入剖析。希望文章对你有所帮助中。
在
SpringMVC
内部,对于@RequestBody
注解的解析过程大致如下所示:
(此处笔者
将对于@RequestBody
解析过程先贴出来,以方便读者理解后续源码分析)
@ReqeustBody
解析逻辑
接下来,笔者便手把手来逐行debug
的方式来剖析@RequestBody
解析的全过程。在开始分析之前希望你明确一点:即对于SpringMVC
中的控制层而言,其相关参数解析会通过InvocableHandlerMethod # getMethodArgumentValues
来完成!!!
本次请求的示例代码如下所示:
java
@PostMapping("/duplicate")
public ResponseEntity getBookAndUserInfo(@RequestBody BookInfo bookInfo ) {
BookInfoDto bookInfoDto = BookInfoDto.builder()
.bookInfo(bookInfo)
.userInfo(userInfo)
.build();
return new ResponseEntity(bookInfoDto, HttpStatus.OK);
}
正如我们之前所说的那样,控制层相关参数解析会通过InvocableHandlerMethod # getMethodArgumentValues
来完成。因此我们的debug
断点最先打在getMethodArgumentValues
中。具体如下所示:
可以看到,在该方法中其首先获取请求路径对应方法中所包含的参数信息,然后逐个进行解析。随后,方法便会通过this.resolvers.resolveArgument
执行到 HandlerMethodArgumentResolverComposite # resolveArgument
内部。
通过上述Debug
我们可以看到,在HandlerMethodArgumentResolverComposite # resolveArgument
的内部,对于@ReqeustBody
的解析,如果其包含有@ReqeustBody
注解其首先会通过getArgumentResolver
方法获取到一个名为RequestResponseBodyMethodProcessor
的解析器。而这里判断是否有@ReqeustBody
注解的思路也很简单,就是通过反射的方式判断方法上是否标有@ReqeustBody
注解。具体细节在此便不过多赘述了。
我们接着往下Debug
便会来到方法RequestResponseBodyMethodProcessor # resolveArgument
内部,此处逻辑具体如下所示:
不难发现,在RequestResponseBodyMethodProcessor # resolveArgument
的方法内部,其会调用一个名为readWithMessageConverters
方法。
一直Debug
便会来到RequestResponseBodyMethodProcessor
父类AbstractMessageConverterMethodArgumentResolver
的readWithMessageConverters
中,该方法的具体逻辑如下:
java
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) {
Object body = NO_VALUE;
// .. 省略其他无关逻辑
EmptyBodyCheckingHttpInputMessage message = null;
try {
// <1> 构建一个EmptyBodyCheckingHttpInputMessage对象
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
// <2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
}
}
// <3> 兜底操作,如果body经过处理器解析后,未被解析则返回null
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
// .. 省略其他无关逻辑
return body;
}
虽然readWithMessageConverters
而言内部逻辑众多,但其实readWithMessageConverters
内部的逻辑其实关键主要有三处:
<1>构建一个EmptyBodyCheckingHttpInputMessage对象
。<2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析
此处逻辑无非就是遍历SpringMVC
容器在加载过程中所加载的默认的HttpMessageConverter
来对内容进行解析处理。<3> 兜底操作,如果body经过处理器解析后,未被解析则返回null
。此处属于兜底操作,不过多赘述!
为此我们重点分析两点,即之前提到的:
<1>构建一个EmptyBodyCheckingHttpInputMessage对象
。其内部逻辑如下所示:
java
private static class EmptyBodyCheckingHttpInputMessage implements HttpInputMessage {
private final HttpHeaders headers;
@Nullable
private final InputStream body;
public EmptyBodyCheckingHttpInputMessage(HttpInputMessage inputMessage) throws IOException {
this.headers = inputMessage.getHeaders();
InputStream inputStream = inputMessage.getBody();
if (inputStream.markSupported()) {
inputStream.mark(1);
this.body = (inputStream.read() != -1 ? inputStream : null);
inputStream.reset();
}
else {
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream);
int b = pushbackInputStream.read();
if (b == -1) {
this.body = null;
}
else {
this.body = pushbackInputStream;
pushbackInputStream.unread(b);
}
}
}
于<1>中提及的EmptyBodyCheckingHttpInputMessage
可能会有些陌生。简单来看,EmptyBodyCheckingHttpInputMessage
在 Spring MVC
内部主要用于与消息转换器(MessageConverter
)一起协同工作。消息转换器负责将请求体的内容转换为方法参数所需的类型,而在处理空请求体时,EmptyBodyCheckingHttpInputMessage
可以确保消息转换器能够正常工作,而不会因为空请求体而出现异常。
可以看到在EmptyBodyCheckingHttpInputMessage
中其主要会读其首先会通过getBody()
方法来获取请求中的请求体信息,进而根据请求中所会否有内容来执行不同逻辑,如果无法读取请求体中的内容则会将输入流body
置为null
。
<2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析
。这部分逻辑如下所示:
java
// <2> 循环遍历SpringMVC中的HttpMessageConverter寻找到合适的处理器来完成解析
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
}
}
这块的逻辑就很简单了,就是遍历SpringMVC
内部各种转换器来完成信息的映射处理。对于@ReqeustBody
转换器的解析全过程可参考之前SpringMVC流程分析(九):从源码解释@ReqeustBody参数无法绑定的问题的分析~
总结
其实在SpringMVC
内部其对于@ReqeustBody
注解的解析本质就是对请求体中Json
格式数据的封装转换,只不过其在解析过程中会牵扯到多个组件的共同联动,进而导致这部分代码看起来很复杂,而本文便抛砖引玉式的对@ReqeustBody
注解的解析流程进行了分析,希望文章对你理解SpringMVC
源码有所帮助~~