深入理解@RequestBody原理
一、RequestBody注解概述
我们通常使用RequestBody注解来web请求中的body体部分内容,并且通常会将其转换为一个javaBean的对象!
如下面的示例文件:
@PostMapping("/school")
public School querySchool(@RequestBody School school){
return school;
}
@Data
public class School {
private String name;
}
示例对象依赖的POM文件为:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>rsa-sign-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rsa-sign-demo</name>
<description>rsa-sign-demo</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.3</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
通过Postman发送请求,得到结果如下:
那么springMVC是如何将Body体中的内容绑定到实体对象School中去的呢?
requestBody注解类注释:
Annotation indicating a method parameter should be bound to the body of the web request.
The body of the request is passed through an {@link HttpMessageConverter} to resolve the method argument depending on the content type of the request.
从上面的描述我们可以看到主要是通过HttpMessageConverter来进行转换,
二、执行流程
所有的springmvc请求都会经过org.springframework.web.servlet.DispatcherServlet#doDispatch来进行处理,我们就从该方法开始跟踪。主要流程图如下所示:
2.1、DispatcherServlet处理
doDispatch方法中的源码如下,只保留我们想进行关注的部分代码:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
//根据请求来来获取对应的处理器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
//根据处理器获取到处理器对应的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 省略部分代码 ...
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 这里使用RequestMappingHandlerAdapter来处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 省略部分代码 ...
}
//省略部分代码 ...
}
2.2、AbstractHandlerMethodAdapter的handle方法
RequestMappingHandlerAdapter是AbstractHandlerMethodAdapter的子类,调用的是其父类的handle方法
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
子类RequestMappingHandlerAdapter的handleInternal方法实现,具体代码如下:
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No synchronization on session demanded at all...
//执行方法的调用
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
接下来跟踪org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod方法
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 省略部分代码...
try {
// 省略部分代码...
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 省略部分代码
// 调用ServletInvocableHandlerMethod的invokeAndHandle
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
2.3、ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 执行请求方法调用
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);
//省略部分代码 ....
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 处理结果返回
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
//省略部分代码 ....
}
在invokeAndHandle方法中,主要干两个事情
- invokeForRequest执行方法参数绑定、执行方法调用,获取返回值
- this.returnValueHandlers.handleReturnValue 对controller层中的方法返回的结果进行处理
接下来我们继续跟踪invokeForRequest方法:
2.4、InvocableHandlerMethod#invokeForRequest
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
invokeForRequest中做了两件事情
- 1、通过getMethodArgumentValues方法获取请求参数
- 2、通过doInvoke使用反射进行controller层方法的调用
然后我们接下来跟踪getMethodArgumentValues方法:
2.5、getMethodArgumentValues方法源码
// InvocableHandlerMethod.java
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取调用的方法中的对应的参数信息
MethodParameter[] parameters = getMethodParameters();
// 如果不接收参数,那么进行直接返回
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS;
}
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
//省略部分代码....
//使用参数解析器来解析对应的参数,这里是HandlerMethodArgumentResolverComposite
args[i] =
this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
//省略部分代码....
}
return args;
}
getMethodArgumentValues方法中的主要逻辑是:
- 1、获取即将调用的controller层方法对应的参数,如果没有参数直接进行返回
- 2、调用参数解析器(HandlerMethodArgumentResolverComposite)来执行参数的解析
接下来的任务是跟踪HandlerMethodArgumentResolverComposite如果从请求中解析对应的参数出来:
2.6 resolveArgument源码跟踪
// HandlerMethodArgumentResolverComposite.java
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//根据调用方法来获取支持的参数解析器
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
// 使用RequestResponseBodyMethodProcessor来解析对应的参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
resolveArgument方法的主要作用是:
- 1、根据对应方法和参数获取支持的方法及参数解析器,这里获取的是RequestResponseBodyMethodProcessor实例对象
- 2、使用RequestResponseBodyMethodProcessor解析方法参数
接下来我们继续跟踪RequestResponseBodyMethodProcessor的resolveArgument方法
2.7、RequestResponseBodyMethodProcessor#resolveArgument
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
// 从httpInputMessage中来读取请求体信息
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
// 省略部分代码...
return adaptArgumentIfNecessary(arg, parameter);
}
resolveArgument方法方法主要部分就是调用了readWithMessageConverters方法
2.8、readWithMessageConverters方法解析
// RequestResponseBodyMethodProcessor.java
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
// 创建一个HttpInputMessage对象
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
//调用另一个重载方法
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
readWithMessageConverters方法主要做了两个事情
- 1、基于原始请求的Request构造一个ServletServerHttpRequest对象
- 2、调用重载的readWithMessageConverters方法来执行真正的参数绑定
2.9、readWithMessageConverters方法解析
// RequestResponseBodyMethodProcessor.java
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
// 省略部分代码 ...
Object body = NO_VALUE;
AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage message = null;
message = new AbstractMessageConverterMethodArgumentResolver.EmptyBodyCheckingHttpInputMessage(inputMessage);
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()) {
//执行所有符合条件的RequestBodyAdvice对应的beforeBodyRead方法
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
//使用MappingJackson2HttpMessageConverter消息转换器来读取对应的消息
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
//执行所有符合条件的RequestBodyAdvice对应的afterBodyRead方法
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
// 省略部分代码 ...
return body;
}
该方法中主要有三件事情进行处理
- 1、调用RequestResponseBodyAdviceChain的beforeBodyRead来修改原始的HttpInputMessage对象
- 2、调用MappingJackson2HttpMessageConverte的read方法来进行数据的读取,这里入参中HttpInputMessage对象为上一步中返回的
- 3、调用RequestResponseBodyAdviceChain的afterBodyRead方法来修改第二步中返回的body
我们继续来回到org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest方法中,来看下获取到参数入参后的处理方法
2.10、invokeForRequest方法执行真正controller层调用
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
该方法主要是使用上面所讲通过MappingJackson2HttpMessageConverte从Request中绑定出来的参数对象进行controller层的方法调用
接下来跟踪下获取到结果返回值后,如何对结果进行处理的,org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle
2.11、invokeAndHandle返回值逻辑处理
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 省略部分代码...
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
//省略部分代码...
}
这里我们核心关注下this.returnValueHandlers.handleReturnValue方法的执行,这个是HandlerMethodReturnValueHandlerComposite对象
2.12、handleReturnValue源码跟踪
//HandlerMethodReturnValueHandlerComposite#handleReturnValue
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 根据返回的值及返回值类型来选择符合条件的HandlerMethodReturnValueHandler对象
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、根据返回的结果和MethodParameter对象来到对应的返回值处理器对象
- 2、使用第一步获取到的对象对返回的值进行真正处理
2.13、selectHandler方法解析
//HandlerMethodReturnValueHandlerComposite.java
@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;
}
上述方法的逻辑的是依次遍历HandlerMethodReturnValueHandlerComposite中所有的HandlerMethodReturnValueHandler对象,一旦有支持的那么就会返回该处理器,遍历完后如果还是没有找到那么就返回null对象。我们这里使用的是RequestResponseBodyMethodProcessor对象,org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsReturnType
// RequestResponseBodyMethodProcessor.java
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
从上面的代码可以看出来,当类层级或者方法层级上有@ResponseBody注解的时候,将会使用RequestResponseBodyMethodProcessor来进行处理
我们来继续跟踪handleReturnValue方法
2.14、handleReturnValue方法解析
// RequestResponseBodyMethodProcessor.java
@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);
}
该方法的核心的就是调用了writeWithMessageConverters方法,接下来跟踪下该方法
2.15、writeWithMessageConverters方法源码解析
protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
// 省略部分代码 ...
if (selectedMediaType != null) {
selectedMediaType = selectedMediaType.removeQualityValue();
for (HttpMessageConverter<?> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
(GenericHttpMessageConverter<?>) converter : null);
//判断当前的HttpMessageConverter是否能够写入该对象
if (genericConverter != null ?
((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
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(logger, traceOn ->
"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
addContentDispositionHeader(inputMessage, outputMessage);
if (genericConverter != null) {
//使用MappingJackson2HttpMessageConverter对象进行写入
genericConverter.write(body, targetType, selectedMediaType, outputMessage);
}
else {
((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Nothing to write: null body");
}
}
return;
}
}
}
// 省略部分代码 ...
}
至此,我们分析完了所有整个流程的代码!
三、源码涉及的到的重点类以及方法总结
3.1 涉及的接口及类
-
1、HandlerMethodArgumentResolver接口: 用来对一个request进行参数解析的接口,如我们使用@RequestBody注解的时候,会使用RequestResponseBodyMethodProcessor来进行处理参数
-
2、RequestResponseBodyMethodProcessor:HandlerMethodArgumentResolver接口的实现类,主要是用来处理在方法参数添加了@RequestBody注解的赋值操作以及在方法上添加了@RequestBody的返回值的处理
-
3、HttpMessageConverter接口: 用来将请求体和响应体进行转换的接口
将请求转换为接口入参,将http处理方法的返回值转换为http响应。
-
4、MappingJackson2HttpMessageConverter实现类:
HttpMessageConverter接口的实现类,用来读取和写入JSON格式类型的请求
-
5、RequestBodyAdvice: 可以对使用@RequestBody注释的参数进行修改
-
6、ResponseBodyAdvice:对使用了@ResponseBody注释的方法或类,用来对请求进行修改
}
``
至此,我们分析完了所有整个流程的代码!
三、源码涉及的到的重点类以及方法总结
3.1 涉及的接口及类
-
1、HandlerMethodArgumentResolver接口: 用来对一个request进行参数解析的接口,如我们使用@RequestBody注解的时候,会使用RequestResponseBodyMethodProcessor来进行处理参数
-
2、RequestResponseBodyMethodProcessor:HandlerMethodArgumentResolver接口的实现类,主要是用来处理在方法参数添加了@RequestBody注解的赋值操作以及在方法上添加了@RequestBody的返回值的处理
-
3、HttpMessageConverter接口: 用来将请求体和响应体进行转换的接口
将请求转换为接口入参,将http处理方法的返回值转换为http响应。
-
4、MappingJackson2HttpMessageConverter实现类:
HttpMessageConverter接口的实现类,用来读取和写入JSON格式类型的请求
-
5、RequestBodyAdvice: 可以对使用@RequestBody注释的参数进行修改
-
6、ResponseBodyAdvice:对使用了@ResponseBody注释的方法或类,用来对请求进行修改