SpringMVC参数绑定原理

继上篇博客记录了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);

image.png

【图1.1】

  • 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;
    }

参数解析过程

获取参数的详细过程

  1. 直接进入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
10
public 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
50
public 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
30
public 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
14
public 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
3.1.png

创建MutablePropertyValues对象化后,进入DataBinder.applyPropertyValues(DataBinder.java line737)。会根据刚刚创建的User对象。创建一个BeanWrapperImpl对象,BeanWrapperImpl实现了PropertyAccessor(属性访问器)接口。这是spring-bean下的一个类,在Sping中,对Bean属性的存取都是通过BeanWrapperImpl类来实现的。BeanWarapperImpl在这里作用就是通过PropertyValue中的属性相关描述,注入到BeanWarapperImpl对应的java对象的属性中去。具体注入的方法是setPropertyValues,这个方法略复杂。它的职责简单总结起来就是根据属性名调用对应的set…方法。比如注入User对象的name属性时,通过反射获取setName方法。如果有该方法便调用。
3.2.png

  • @RequestParam
    image.png
  • @PathVariable
    image.png
  • @RequestHeader
    image.png
  • @ModelAttribute
    image.png

原理

所有的参数解析器实现了HandlerMethodArgumentResolver

1
2
3
4
5
  // 判断是否支持改参数的解析  
boolean supportsParameter(MethodParameter var1);

// 真正实现解析参数的逻辑(从request中,或者从header中拿出参数)
Object resolveArgument(MethodParameter var1, ModelAndViewContainer var2, NativeWebRequest var3, WebDataBinderFactory var4) throws Exception;

时序图

image.png
链接:https://www.processon.com/view/link/5b6da522e4b067df5a041f85

总结

  • 服务端使用@requestBody,客户端发送json数据,无法接受基本类型。可以使用JSONObject对象来接收,或者Dto。(经过同事指正,这里不能用JSONObject,会让下个维护的同事一脸懵)
  • 接受对象类型的参数时,明确知道传输的参数字段不为空才用@RequestBody,否则使用@ModelAttribute

    1
    2
    3
    4
    5
    6
    7
    if (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());
}