继上篇博客记录了SpringMVC接收参数的几种方式后,最近有空看了一下源码,同时也在公司小组内分享了一下。本篇博客作为参数解析读源码的记录。
SpringMVC参数绑定原理
常见的注解使用姿势
A、处理requet uri 部分(这里指uri template中variable,不含queryString部分)的注解:@PathVariable;
B、处理request header部分的注解: @RequestHeader, @CookieValue;
C、处理request body部分的注解:@RequestParam, @RequestBody;
D、处理attribute类型是注解: @SessionAttributes, @ModelAttribute;
springMVC请求处理流程
我们先看一下一个请求是怎么被执行并且返回的
请求进入DispatcherServlet的doDispatch后,HandlerExecutionChain。然后根据HandlerExecutionChain来确认HandlerApapter,确认后执行HandlerAdapter的handle方法。
1
2
3
4
5
6
7
8
9
10
11
12// line 897
doDispatch(request, response);
// Determine handler for the current request.
// RequestMapping标注的路由都会被spring的RequestMappingHandlerMapping 维护在map中,【图1.1】这里从map种取出
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
// line 943 Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// line 963 Actually invoke the handler. 执行请求并且返回ModelAndView
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());RequestMappingHandlerAdapter
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// line 797 初始化 HandlerMethod 把【图1.1】中的 handlerMethod 包装一层,然后初始化一些参数解析器,返回值解析器等
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
//从找到Controller类中找到被InitBinder修饰的方法,然后把这些方法作为参数initBinderMethods
invocableMethod.setDataBinderFactory(binderFactory);
// jdk1.8之前通过反射接口无法得到参数名,spring默认通过org.springframework.core.DefaultParameterNameDiscoverer获取参数名
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 看这里可以实现用session来锁住用户的操作 见附录1.1
// 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 {
// line 738 No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
// line 827
invocableMethod.invokeAndHandle(webRequest, mavContainer);
- invokeAndHandle() -> ServletInvocableHandlerMethod类的invokeAndHandle() -> InvocableHandlerMethod类 invokeForRequest()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// ServletInvocableHandlerMethod line 116
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// 最后真正用反射调用controller的方法
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 解析参数
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
"' with arguments " + Arrays.toString(args));
}
// 带着参数调用方法返回方法返回值
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
"] returned [" + returnValue + "]");
}
return returnValue;
}
参数解析过程
获取参数的详细过程
- 直接进入getMethodArgumentValues方法看看其过程,代码如下
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/**
* 获取当前请求的方法参数值。
*/
private Object[] getMethodArgumentValues(
NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//获取方法参数数组
MethodParameter[] parameters = getMethodParameters();
//创建一个参数数组,保存从request解析出的方法参数
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(parameterNameDiscoverer);
GenericTypeResolver.resolveParameterType(parameter, getBean().getClass());
// 处理异常的参数从这里获取? 待模拟
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
// 2.1遍历之前RequestMappingHandlerAdapter初始化的那24个HandlerMethodArgumentResolver(参数解析器),是否存在支持该参数解析的解析器,存在加入缓存(param-> resolver)
if (argumentResolvers.supportsParameter(parameter)) {
try {
// 2.2 从缓存种取出2.1存进去的resolver 缓存不存在 遍历24种默认的解析器 附录2.1
args[i] = argumentResolvers.resolveArgument(parameter, mavContainer, request, dataBinderFactory);
continue;
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(getArgumentResolutionErrorMessage("Error resolving argument", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
String msg = getArgumentResolutionErrorMessage("No suitable resolver for argument", i);
throw new IllegalStateException(msg);
}
}
return args;
}
2.进入HandlerMethodArgumentResolverComposite的resolveArgument方法1
2
3
4
5
6
7
8
9
10public Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception {
// 2.2 从缓存中取出resolver
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
Assert.notNull(resolver, "Unknown parameter type [" + parameter.getParameterType().getName() + "]");
// 2.3 解析参数
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
3.然后更具参数类型调用24中解析的resolverArgument方法实现
绑定简单类型参数: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
50public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
// 2.4 调用子类(即24个默认resolve)的解析参数的方法 从请求种解析参数
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
// 处理注解中给的默认值
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
// 判断requird属性,不存在参数抛异常,就是我们常见的BadRequest
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
// request中拿到的都是String类型,如果有必要就绑定,解析参数类型
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
这个方法的职责是,首先获取paramType。也就是int对应的Class对象。然后根据parameter对象创建一个NamedValueInfo对象。这个对象存放的就是参数名、是否必须、参数默认值3个成员变量。然后进入resolverName方法解析参数,里面的逻辑其实很简单,就是根据方法的参数名来获取request中的参数。
绑定对象类型参数(ModelAttribute):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
30public final Object resolveArgument(
MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest request, WebDataBinderFactory binderFactory)
throws Exception {
String name = ModelFactory.getNameForParameter(parameter);
Object attribute = (mavContainer.containsAttribute(name)) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, request);
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) {
//将请求绑定至目标binder的参数对象 上文attribute。 比如 json 转dto就在这里完成 要求参数是复杂类型 否则不走绑定逻辑
bindRequestParameters(binder, request);
//如果有验证,则验证参数
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
return binder.getTarget();
}
来看下具体绑定流程:1
2
3
4
5
6
7
8
9
10
11
12
13
14public void bind(WebRequest request) {
MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap());
if (this.isMultipartRequest(request) && request instanceof NativeWebRequest) {
MultipartRequest multipartRequest = (MultipartRequest)((NativeWebRequest)request).getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
} else if (servlet3Parts) {
HttpServletRequest serlvetRequest = (HttpServletRequest)((NativeWebRequest)request).getNativeRequest(HttpServletRequest.class);
(new WebRequestDataBinder.Servlet3MultipartHelper(this.isBindEmptyMultipartFiles())).bindParts(serlvetRequest, mpvs);
}
}
this.doBind(mpvs);
}
该方法的职责是实例化一个parameterType的对象,然后根据request和attribute、name创建一个WebDataBinder对象,其中。然后进入bindRequestParameters方法绑定,根据reqeust中的参数创建一个MutablePropertyValues对象。MutablePropertyValues里面存放了一个或多个PropertyValue,其中PropertyValue用于保存单个bean属性的相关信息,比如参数名、参数值。这里需要注意的是PropertyValue并不是保存request对象的所有参数属性信息。而是一个参数属性对应一个PropertyValue。比如这里的reqeust对象,携带了两个参数,name和age
创建MutablePropertyValues对象化后,进入DataBinder.applyPropertyValues(DataBinder.java line737)。会根据刚刚创建的User对象。创建一个BeanWrapperImpl对象,BeanWrapperImpl实现了PropertyAccessor(属性访问器)接口。这是spring-bean下的一个类,在Sping中,对Bean属性的存取都是通过BeanWrapperImpl类来实现的。BeanWarapperImpl在这里作用就是通过PropertyValue中的属性相关描述,注入到BeanWarapperImpl对应的java对象的属性中去。具体注入的方法是setPropertyValues,这个方法略复杂。它的职责简单总结起来就是根据属性名调用对应的set…方法。比如注入User对象的name属性时,通过反射获取setName方法。如果有该方法便调用。
- @RequestParam
- @PathVariable
- @RequestHeader
- @ModelAttribute
原理
所有的参数解析器实现了HandlerMethodArgumentResolver
1
2
3
4
5 // 判断是否支持改参数的解析
boolean supportsParameter(MethodParameter var1);
// 真正实现解析参数的逻辑(从request中,或者从header中拿出参数)
Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;
时序图
链接:https://www.processon.com/view/link/5b6da522e4b067df5a041f85
总结
- 服务端使用@requestBody,客户端发送json数据,无法接受基本类型。可以使用JSONObject对象来接收,或者Dto。(经过同事指正,这里不能用JSONObject,会让下个维护的同事一脸懵)
接受对象类型的参数时,明确知道传输的参数字段不为空才用@RequestBody,否则使用@ModelAttribute
1
2
3
4
5
6
7if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && inputMessage.getBody() == null)) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}抽象与封装的思想,本来24中解析器都要实现HandlerMethodArgumentResolver接口,但是其中有几种有共性的代码,就抽出一个抽象类来实现目标接口,有共性的类来继承这个抽象类,实现抽象方法。
- 遇到参数报错的时候,找2个地方,1,用那个解析器解析的 2,看解析流程 一般400 或者415都是解析流程报错。比如requestBody注解不能解析表单类型的参数。
附录
1.1 Spring Bean 后置处理器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22@Component
public class MyPostProcessor implements BeanPostProcessor
{
@Override
public Object postProcessBeforeInitialization( Object bean, String name ) throws BeansException
{
// bean 实例化之后,完成依赖注入之前 调用
if( bean instanceof RequestMappingHandlerAdapter )
{
RequestMappingHandlerAdapter adapter = ( RequestMappingHandlerAdapter ) bean;
adapter.setSynchronizeOnSession( true );
}
return bean;
}
@Override
public Object postProcessAfterInitialization( Object bean, String beanName ) throws BeansException
{
return bean;
}
}
2.1 默认的24中参数解析器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//默认的参数解析,创建了默认的24个参数解析器,并添加至resolvers
//这里的24个参数解析器都是针对不同的参数类型来解析的
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();
// 基于注解的参数解析器
//一般用于带有@RequestParam注解的简单参数绑定,简单参数比如byte、int、long、double、String以及对应的包装类型
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
//用于处理带有@RequestParam注解,且参数类型为Map的解析绑定
resolvers.add(new RequestParamMapMethodArgumentResolver());
//一般用于处理带有@PathVariable注解的默认参数绑定
resolvers.add(new PathVariableMethodArgumentResolver());
//也是用于带有@PathVariable注解的Map相关参数绑定
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters()));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters()));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
// 基于类型的参数解析器
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters()));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}