【Spring】@RequestBody的实现原理
@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的核心,它对请求进行调度,收到请求后会进入DispatcherServlet的doDispatch方法中:
- 调用
getHandler方法获取请求对应的Handler处理器; - 根据
handler获取对应的适配器,这里用到了适配器模式; - 调用适配器的
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;
}
所以回到RequestMappingHandlerAdapter的handleInternal方法,里面调用了invokeHandlerMethod方法进行处理:
- 创建
ServletInvocableHandlerMethod; - 调用invokeAndHandle方法继续请求处理;
 - 调用
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();
		}
	}
}
ServletInvocableHandlerMethod的invokeAndHandle中调用了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方法获取请求中的参数,处理逻辑如下:
调用
getMethodParameters获取方法中的参数,也就是我们的请求处理器方法中的所有参数,上面看到submit只接收了一个UserInfo类型的参数,这里可以从断点中看到parameters中只有一个元素,类型为UserInfo:

对获取到方法中的所有参数进行遍历,通过处理器调用
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中实现:
调用
getArgumentResolver方法获取对应的参数处理器resolver;调用
resolver的resolveArgument方法进行参数解析;
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);
	}
}
从断点中可以看到此时的resolver是RequestResponseBodyMethodProcessor类型的:

进入到RequestResponseBodyMethodProcessor的resolveArgument方法中,它又调用了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方法处理逻辑如下:
遍历所有HTTP消息转换器,判断是否支持解析当前的请求参数类型;
如果转换器支持解析当前的参数类型并且有消息体内容,调用转换器的
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类型会进入到MappingJackson2HttpMessageConverter,read方法在其父类AbstractJackson2HttpMessageConverter,处理逻辑如下:
获取参数的Class类型,从断点中可以看出是[class com.example.demo.model.UserInfo];

调用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的实现原理的更多相关文章
- 关于Spring @RequestBody 自动映射模型原理
		
关于Spring @RequestBody 自动映射模型 2016年10月18日 22:17:12 稻子丶 阅读数:5049 在很多时候,Spring的注解为我们提供了很多方便,但只知道其用法,不 ...
 - Spring aop的实现原理
		
简介 前段时间写的java设计模式--代理模式,最近在看Spring Aop的时候,觉得于代理模式应该有密切的联系,于是决定了解下Spring Aop的实现原理. 说起AOP就不得不说下OOP了,OO ...
 - Spring、Spring依赖注入与编码剖析Spring依赖注入的原理
		
Spring依赖注入 新建PersonIDao 和PersonDao底实现Save方法: public interface PersonIDao { public void save(); } pub ...
 - Spring框架和MVC原理
		
Spring框架和MVC原理 目录 Spring框架 SpringMVC工作原理 参考资料 回到顶部 Spring框架 Spring当前框架有20个jar包,大致可以分为6大模块: Core Cont ...
 - spring的MVC执行原理
		
spring的MVC执行原理 1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作. 2.DispatcherSer ...
 - 撸一撸Spring Cloud Ribbon的原理-负载均衡器
		
在上一篇<撸一撸Spring Cloud Ribbon的原理>中整理发现,RestTemplate内部调用负载均衡拦截器,拦截器内最终是调用了负载均衡器来选择服务实例. 接下来撸一撸负载均 ...
 - Spring AOP异常捕获原理
		
Spring AOP异常捕获原理: 被拦截的方法,须显式的抛出异常,且不能做任何处理, 这样AOP才能捕获到方法中的异常,进而进行回滚. 换句话说,就是在Service层的 ...
 - 通俗的讲法理解spring的事务实现原理
		
拿房屋买卖举例,流程:销售房屋 -- 接待员 -- 销售员 -- 财务 售楼处 存放着所有待售和已售的房屋数据(数据源 datasource) 总经理 带领一套自己的班底,下属员工都听自己的,服务于售 ...
 - 撸一撸Spring Cloud Ribbon的原理-负载均衡策略
		
在前两篇<撸一撸Spring Cloud Ribbon的原理>,<撸一撸Spring Cloud Ribbon的原理-负载均衡器>中,整理了Ribbon如何通过负载均衡拦截器植 ...
 - Spring Boot自动配置原理、实战
		
Spring Boot自动配置原理 Spring Boot的自动配置注解是@EnableAutoConfiguration, 从上面的@Import的类可以找到下面自动加载自动配置的映射. org.s ...
 
随机推荐
- itext 生成pdf ----hello world
			
iText是Java中用于创建和操作PDF文件的开源库.它是由Bruno Lowagie.Paulo Soares等人编写的.Ohloh报告称2001年以来[2],26个不同的贡献者进行了1万多次 ...
 - CISP_PTE学习
			
一.http协议的基础知识(请求方法.状态码.响应头信息.协议的URL) 1.请求方法: (1) http1.0请求包含 head.get.post (2)http1.1请求包含head.get.po ...
 - day09-达人探店
			
功能04-达人探店 5.功能04-达人探店 5.1发布&查看探店笔记 5.1.1发布探店笔记 探店笔记类似点评网站的评价,往往是图文结合.对应的表有两个: tb_blog:探店笔记表,包含笔记 ...
 - Prism Sample 16-RegionContext
			
终于发现一个有趣的新知识了. 本例的核心是RegionContext,意思是一个区域的上下文.但与DataContext似乎并不相同. 先看一下整体思路. 在主窗体上只有一个Region: <G ...
 - 解析草稿-造价管理-工程经济-P190-例4.2.3
			
原题 计算步骤 需要记忆的概念 excel计算文件 [腾讯文档]例题
 - 2022-11-18:给定一个数组arr,表示连续n天的股价,数组下标表示第几天 指标X:任意两天的股价之和 - 此两天间隔的天数 比如 第3天,价格是10 第9天,价格是30 那么第3天和第9天的指
			
2022-11-18:给定一个数组arr,表示连续n天的股价,数组下标表示第几天 指标X:任意两天的股价之和 - 此两天间隔的天数 比如 第3天,价格是10 第9天,价格是30 那么第3天和第9天的指 ...
 - 2022-08-05:以下go语言代码输出什么?A:65, string;B:A, string;C:65, int;D:报错。
			
2022-08-05:以下go语言代码输出什么?A:65, string:B:A, string:C:65, int:D:报错. package main import ( "fmt&quo ...
 - 2022-06-15:薯队长最近在参加了一个活动,主办方提供了N个礼物以供挑选, 每个礼物有一个价值,范围在0 ~ 10^9之间, 薯队长可以从中挑选k个礼物。 返回:其中价值最接近的两件礼物之间相差
			
2022-06-15:薯队长最近在参加了一个活动,主办方提供了N个礼物以供挑选, 每个礼物有一个价值,范围在0 ~ 10^9之间, 薯队长可以从中挑选k个礼物. 返回:其中价值最接近的两件礼物之间相差 ...
 - 2022-03-09:我们正在玩一个猜数游戏,游戏规则如下: 我从 1 到 n 之间选择一个数字。 你来猜我选了哪个数字。 如果你猜到正确的数字,就会 赢得游戏 。 如果你猜错了,那么我会告诉你,我选
			
2022-03-09:我们正在玩一个猜数游戏,游戏规则如下: 我从 1 到 n 之间选择一个数字. 你来猜我选了哪个数字. 如果你猜到正确的数字,就会 赢得游戏 . 如果你猜错了,那么我会告诉你,我选 ...
 - 【Haxe】(二)字符串与变量的输入输出
			
前言 每次学习一门新语言,各种手册和教程一上来就是讲变量如何定义,数据结构怎么用,很少有讲输入输出应该怎么写的.我比较喜欢先搞懂这部分,这让我感觉像是掌握了学习主动权,很能调动我的学习积极性.于是我的 ...