@RequestBody注解可以用于POST请求接收请求体中的参数,使用方式如下:

@Controller
public class IndexController { @PostMapping(value = "/submit", produces = MediaType.APPLICATION_JSON_VALUE)
public void submit(@RequestBody UserInfo userInfo) {
System.out.println(userInfo.toString());
}
}

那么是如何从请求中解析数据设置到对应的参数中呢,接下来就从源码的角度一探究竟。

DispatcherServlet是Spring MVC的核心,它对请求进行调度,收到请求后会进入DispatcherServletdoDispatch方法中:

  1. 调用getHandler方法获取请求对应的Handler处理器;
  2. 根据handler获取对应的适配器,这里用到了适配器模式;
  3. 调用适配器的handle方法处理请求,它会返回一个ModelAndView对象;
public class DispatcherServlet extends FrameworkServlet {
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 {
// 检查是否有Multipart
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request); // 根据请求获取对应的处理器
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
} // 根据handler获取对应的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // ... // 处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // ...
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// ...
}
}

通过POSTMAN模拟请求,在代码中打断点可以看到HandlerAdapter的类型为对RequestMappingHandlerAdapter:

handle方法在其父类AbstractHandlerMethodAdapter中实现,在它的handle方法中,又调用了handleInternal方法处理请求,handleInternal是一个抽象方法,由具体的子类实现:

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 处理请求
return handleInternal(request, response, (HandlerMethod) handler);
} @Nullable
protected abstract ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception;
}

所以回到RequestMappingHandlerAdapterhandleInternal方法,里面调用了invokeHandlerMethod方法进行处理:

  1. 创建ServletInvocableHandlerMethod
  2. 调用invokeAndHandle方法继续请求处理;
  3. 调用getModelAndView方法返回ModelAndView;
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav;
checkRequest(request);
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
// 执行请求
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 执行请求
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 执行请求
mav = invokeHandlerMethod(request, response, handlerMethod);
}
// ...
return mav;
} @Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// ...
// 创建ServletInvocableHandlerMethod
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 调用invokeAndHandle方法处理请求
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 返回ModelAndView
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
}

ServletInvocableHandlerMethodinvokeAndHandle中调用了invokeForRequest方法执行请求,它的实现在其父类InvocableHandlerMethod中:

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 执行请求
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest); // ...
}
}

invokeForRequest中又调用了getMethodArgumentValues方法获取请求中的参数,处理逻辑如下:

  1. 调用getMethodParameters获取方法中的参数,也就是我们的请求处理器方法中的所有参数,上面看到submit只接收了一个UserInfo类型的参数,这里可以从断点中看到parameters中只有一个元素,类型为UserInfo:

  2. 对获取到方法中的所有参数进行遍历,通过处理器调用resolveArgument方法解析请求中的数据,解析每一个参数对应的值;

public class InvocableHandlerMethod extends HandlerMethod {
@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);
} 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++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// ...
try {
// 调用resolveArgument从请求中解析对应的数据
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
// ...
}
return args;
}
}

resolveArgument方法在HandlerMethodArgumentResolverComposite中实现:

  1. 调用getArgumentResolver方法获取对应的参数处理器resolver

  2. 调用resolverresolveArgument方法进行参数解析;

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
@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.");
}
// 解析参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
}

从断点中可以看到此时的resolverRequestResponseBodyMethodProcessor类型的:

进入到RequestResponseBodyMethodProcessorresolveArgument方法中,它又调用了readWithMessageConverters方法解析参数,最终会进入到

AbstractMessageConverterMethodArgumentResolve中的readWithMessageConverters方法:

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { parameter = parameter.nestedIfOptional();
// 通过转换器进行参数解析
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
// ...
return adaptArgumentIfNecessary(arg, parameter);
} @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");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
// 调用AbstractMessageConverterMethodArgumentResolver中readWithMessageConverters方法读取参数
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. 遍历所有HTTP消息转换器,判断是否支持解析当前的请求参数类型;

  2. 如果转换器支持解析当前的参数类型并且有消息体内容,调用转换器的read方法进行解析;

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { // ...
try {
message = new 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()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
// 调用read方法进行读取
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
} // ...
return body;
}
}

这里列举一些消息转换器的类型:

对于application/json;charset=UTF-8类型会进入到MappingJackson2HttpMessageConverterread方法在其父类AbstractJackson2HttpMessageConverter,处理逻辑如下:

  1. 获取参数的Class类型,从断点中可以看出是[class com.example.demo.model.UserInfo];

  2. 调用readJavaType方法解析参数

    (1)获取ContentType,前面可以看到请求接收的类型为application/json;

    (2)获取字符集,这里的字符集为UTF-8;

    (3)创建ObjectMapper对象,并从请求体中读取JSON数据,转为JAVA对象;

public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {

    @Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
// 获取参数的Class类型
JavaType javaType = getJavaType(type, contextClass);
// 解析参数
return readJavaType(javaType, inputMessage);
} private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
// 获取ContentType
MediaType contentType = inputMessage.getHeaders().getContentType();
// 获取字符集
Charset charset = getCharset(contentType);
ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);
boolean isUnicode = ENCODINGS.containsKey(charset.name());
try {
// ...
if (isUnicode) {
// 获取HTTP请求体中的JSON数据,转为JAVA对象
return objectMapper.readValue(inputMessage.getBody(), javaType);
}
else {
Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
return objectMapper.readValue(reader, javaType);
}
}
// ....
}
}

到这里已经成功从HTTP请求体中的JSON数据,并转为JAVA对象,完成了参数的设置。

Spring版本:5.3.4

【Spring】@RequestBody的实现原理的更多相关文章

  1. 关于Spring @RequestBody 自动映射模型原理

    关于Spring @RequestBody 自动映射模型 2016年10月18日 22:17:12 稻子丶 阅读数:5049   在很多时候,Spring的注解为我们提供了很多方便,但只知道其用法,不 ...

  2. Spring aop的实现原理

    简介 前段时间写的java设计模式--代理模式,最近在看Spring Aop的时候,觉得于代理模式应该有密切的联系,于是决定了解下Spring Aop的实现原理. 说起AOP就不得不说下OOP了,OO ...

  3. Spring、Spring依赖注入与编码剖析Spring依赖注入的原理

    Spring依赖注入 新建PersonIDao 和PersonDao底实现Save方法: public interface PersonIDao { public void save(); } pub ...

  4. Spring框架和MVC原理

    Spring框架和MVC原理 目录 Spring框架 SpringMVC工作原理 参考资料 回到顶部 Spring框架 Spring当前框架有20个jar包,大致可以分为6大模块: Core Cont ...

  5. spring的MVC执行原理

    spring的MVC执行原理 1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作. 2.DispatcherSer ...

  6. 撸一撸Spring Cloud Ribbon的原理-负载均衡器

    在上一篇<撸一撸Spring Cloud Ribbon的原理>中整理发现,RestTemplate内部调用负载均衡拦截器,拦截器内最终是调用了负载均衡器来选择服务实例. 接下来撸一撸负载均 ...

  7. Spring AOP异常捕获原理

    Spring AOP异常捕获原理:        被拦截的方法,须显式的抛出异常,且不能做任何处理, 这样AOP才能捕获到方法中的异常,进而进行回滚.        换句话说,就是在Service层的 ...

  8. 通俗的讲法理解spring的事务实现原理

    拿房屋买卖举例,流程:销售房屋 -- 接待员 -- 销售员 -- 财务 售楼处 存放着所有待售和已售的房屋数据(数据源 datasource) 总经理 带领一套自己的班底,下属员工都听自己的,服务于售 ...

  9. 撸一撸Spring Cloud Ribbon的原理-负载均衡策略

    在前两篇<撸一撸Spring Cloud Ribbon的原理>,<撸一撸Spring Cloud Ribbon的原理-负载均衡器>中,整理了Ribbon如何通过负载均衡拦截器植 ...

  10. Spring Boot自动配置原理、实战

    Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...

随机推荐

  1. 线上诊断神器-arthas基本应用

    Arthas基本应用 一.Arthas作用 什么是Arthas呢? ​ Arthas 是一款阿里推出的线上监控诊断产品,通过全局视角实时查看应用 load.内存.gc.线程的状态信息,并能在不修改应用 ...

  2. 洛谷:P5716日份天数

    题目描述 输入年份和月份,输出这一年的这一月有多少天.需要考虑闰年. 输入格式 输入两个正整数,分别表示年份 \(y\) 和月数 \(m\),以空格隔开. 输出格式 输出一行一个正整数,表示这个月有多 ...

  3. Arnold置乱

    一.Arnold置乱概述 Arnold变换是俄国数学家弗拉基米尔·阿诺德(Vladimir Igorevich Arnold)提出,Arnold将其应用在遍历理论研究中.由于Arnold本人最初对一张 ...

  4. 笔记十:线程间的通信(pthread_exit()和pthread_join())

    linux高级编程之线程间的通信(pthread_exit()和pthread_join()) 1.线程终止      如果进程中的任一线程调用了exit._Exit或者_exit,那么整个进程就会终 ...

  5. SpringBoot如何自定义一个starter

    SpringBoot starter,大家应该在平常写项目中应该非常熟悉,很多依赖都会提供集成SpringBoot的依赖,这样我们用起来就非常顺手,开箱就能用,那如何自定义一个starter呢? Sp ...

  6. 2021-07-05:股票问题2。给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖

    2021-07-05:股票问题2.给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格.设计一个算法来计算你所能获取的最大利润.你可以尽可能地完成更多的交易(多次买卖 ...

  7. 2021-12-14:根据身高重建队列。 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个

    2021-12-14:根据身高重建队列. 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序).每个 people[i] = [hi, ki] 表示第 i 个 ...

  8. 2021-08-02:按公因数计算最大组件大小。给定一个由不同正整数的组成的非空数组 A,考虑下面的图:有 A.length 个节点,按从 A[0] 到 A[A.length - 1] 标记;只有当

    2021-08-02:按公因数计算最大组件大小.给定一个由不同正整数的组成的非空数组 A,考虑下面的图:有 A.length 个节点,按从 A[0] 到 A[A.length - 1] 标记:只有当 ...

  9. docker安装go-fastdfs

    1.docker命令安装 docker run -d --name fastdfs -p 8180:8080 sjqzhang/go-fastdfs 2.浏览器访问 http://192.168.20 ...

  10. 谷歌语法Github及利用方式

    0x01简介 GoogleHack(谷歌语法)是指使用Google等搜索引擎对某些特定的网络主机漏洞(通常是服务器上的脚本漏洞)进行搜索,以达到快速找到漏洞主机或特定主机的漏洞的目的.比如使用搜索包含 ...