1.1 前言

  • 回顾上一章节的方法参数解析器的入口,可以看到Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);这行代码是获取方法参数值,这里是返回的是个数组, getMethodArgumentValues(request, mavContainer, providedArgs)里面的逻辑就是遍历每一个参数进行解析
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest
public Object invokeForRequest(NativeWebRequest request, org.springframework.web.method.support.ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {

// 1、这里得到了方法参数值
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
"' with arguments " + Arrays.toString(args));
}
// 2、传入方法参数值并执行方法
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
"] returned [" + returnValue + "]");
}
// 3、返回结果
return returnValue;
}

/**
* 根据当前请求获取方法的请求参数
* Get the method argument values for the current request.
* 先是判断相应类型的参数已经在providedArgs中提供了,如果有的话就是直接返回,否则则使用argumentResolvers解析
*
*/
private Object[] getMethodArgumentValues(NativeWebRequest request, org.springframework.web.method.support.ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 获取方法的参数,在HanderMethod中
MethodParameter[] parameters = getMethodParameters();
// 用于保存解析出参数的值
Object[] args = new Object[parameters.length];
// 遍历每一个参数进行解析
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
// 给Parameter设置参数名解析器
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
// 如果相应类型的参数已经在providedArgs中提供了,则直接设置到parameter
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
// 使用argumentResolvers解析参数
args[i] = this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}
// 解析不出来,抛异常
if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " +
parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}
  • Spring Mvc的方法参数解析是交给HandlerMethodArgumentResolver来实现的,由下面可以看到是这个接口就两个方法,第一个方法是判断解析器是否支持该参数,第二个方法是根据request并将http的请求参数解析为Java Bean
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
/**
* 方法参数解析器
*
* Strategy interface for resolving method parameters into argument values in
* the context of a given request.
*
* @author Arjen Poutsma
* @since 3.1
* @see HandlerMethodReturnValueHandler
*/
public interface HandlerMethodArgumentResolver {

/**
* 是否支持
*
* Whether the given {@linkplain MethodParameter method parameter} is
* supported by this resolver.
* @param parameter the method parameter to check
* @return {@code true} if this resolver supports the supplied parameter;
* {@code false} otherwise
*/
boolean supportsParameter(MethodParameter parameter);

/**
* 根据request解析方法参数值
*
* Resolves a method parameter into an argument value from a given request.
* A {@link ModelAndViewContainer} provides access to the model for the
* request. A {@link WebDataBinderFactory} provides a way to create
* a {@link WebDataBinder} instance when needed for data binding and
* type conversion purposes.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to {@link #supportsParameter} which must
* have returned {@code true}.
* @param mavContainer the ModelAndViewContainer for the current request
* @param webRequest the current request
* @param binderFactory a factory for creating {@link WebDataBinder} instances
* @return the resolved argument value, or {@code null}
* @throws Exception in case of errors with the preparation of argument values
*/
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;

}
  • 通过调试可以看到有下面这些argumentResolvers,看类名称是不是很熟悉,就是我们平常使用的@RequestBody @RequestParam 是一一对应的,还是专人做专事,可以得出不同的参数是有不同的参数解析组件来专门处理的
  • 下面来看主要XXXArgumentResolver的作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. SessionAttributeMethodArgumentResolver
针对 被 @SessionAttribute 修饰的参数起作用, 参数的获取一般通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_SESSION)
2. RequestParamMethodArgumentResolver
针对被 @RequestParam 注解修饰, 但类型不是 Map, 或类型是 Map, 并且 @RequestParam 中指定 name, 一般通过 MultipartHttpServletRequest | HttpServletRequest 获取数据
3. RequestHeaderMethodArgumentResolver
针对 参数被 RequestHeader 注解, 并且 参数不是 Map 类型, 数据通过 HttpServletRequest.getHeaderValues(name) 获取
4. RequestAttributeMethodArgumentResolver
针对 被 @RequestAttribute 修饰的参数起作用, 参数的获取一般通过 HttpServletRequest.getAttribute(name, RequestAttributes.SCOPE_REQUEST)
5. PathVariableMethodArgumentResolver
解决被注解 @PathVariable 注释的参数 <- 这个注解对应的是 uri 中的数据, 在解析 URI 中已经进行解析好了 <- 在 RequestMappingInfoHandlerMapping.handleMatch -> getPathMatcher().extractUriTemplateVariables
6. MatrixVariableMethodArgumentResolver
针对被 @MatrixVariable 注解修饰的参数起作用, 从 HttpServletRequest 中获取去除 ; 的 URI Template Variables 获取数据
7. ExpressionValueMethodArgumentResolver
针对被 @Value 修饰, 返回 ExpressionValueNamedValueInfo
8. ServletCookieValueMethodArgumentResolver
针对被 @CookieValue 修饰, 通过 HttpServletRequest.getCookies 获取对应数据

2.1 解析

2.1.1 PathVariableMethodArgumentResolver

  • 这个ArgumentResolver解析类是解析被注解 @PathVariable 注释的参数,这个注解会把url上面的值解析到对应的方法参数上,比如下面的例子,参数id会解析为1,参数name会解析为name
1
2
3
4
5
6
7
8
// http://127.0.0.1/user/view/1/songsy

@GetMapping("/view/{id}/{name}")
public ResponseMO view(@PathVariable Integer id, @PathVariable String name) {
IUser iUser = userService.selectByPrimaryKey(id);
iUser.setUsername(name);
return success(iUser);
}
  • 下面来看Spring MVC是怎样把url上面的值解析到对应的方法参数上,首先要获取url上面的值,那么是在哪里获取的呢?,回顾之前的org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod方法,这个方法是查找当前request请求 最为匹配的处理方法HandlerMethod,如果有多个匹配结果,则选择最佳匹配结果
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
51
52
53
54
55
/**
* 查找当前request请求 最为匹配的处理方法HandlerMethod,如果有多个匹配结果,则选择最佳匹配结果
*
* Look up the best-matching handler method for the current request.
* If multiple matches are found, the best match is selected.
* @param lookupPath mapping lookup path within the current servlet mapping
* @param request the current request
* @return the best-matching handler method, or {@code null} if no match
* @see #handleMatch(Object, String, HttpServletRequest)
* @see #handleNoMatch(Set, String, HttpServletRequest)
*/
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
// 根据URL来获取,springMVC会在初始化的时候建立URL和相应RequestMappingInfo的映射。如果不是restful接口,这里就可以直接获取到了
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 匹配校验 根据directPathMatches获取到List<Match> matches中
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 全盘扫描
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
// 得到匹配结果
if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
// 如果最佳匹配 第二佳匹配都是同一个则报错
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
// 设置HttpServletRequest值 如解析url上的属性值
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
// 没有找到匹配,返回null
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
  • 关注handleMatch(bestMatch.mapping, lookupPath, request);方法,看注释可以看到是Expose URI template variables,这里主要是对HttpServletRequest进行setAttribute
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
/**
* Expose URI template variables, matrix variables, and producible media types in the request.
* @see HandlerMapping#URI_TEMPLATE_VARIABLES_ATTRIBUTE
* @see HandlerMapping#MATRIX_VARIABLES_ATTRIBUTE
* @see HandlerMapping#PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE
*/
@Override
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);

String bestPattern;
Map<String, String> uriVariables;
Map<String, String> decodedUriVariables;

Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
decodedUriVariables = Collections.emptyMap();
}
else {
bestPattern = patterns.iterator().next();
// 获取url上的参数值
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
}

request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);

if (isMatrixVariableContentAvailable()) {
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
}

if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
  • 注意下面这个方法,可以看到是获取了url上面的参数值封装为Map<String, String>并赋值到request
1
2
3
4
5
6
7
8
9
10
11
12
Map<String, String> uriVariables;
Map<String, String> decodedUriVariables;

...

// 获取url上的参数值
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);

...

request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);
  • 到这里HttpServletRequest已经有参数值了,现在是如何赋值到对应的参数中,这里我们的PathVariableMethodArgumentResolver就发挥作用了
  • 我们关注它是怎么实现HandlerMethodArgumentResolver接口的两个方法的

    • supportsParameter()方法

      • 代码如下,可以看到是看参数值有没有被PathVariable注解修饰
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
        if (!parameter.hasParameterAnnotation(PathVariable.class)) {
        return false;
        }
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
        String paramName = parameter.getParameterAnnotation(PathVariable.class).value();
        return StringUtils.hasText(paramName);
        }
        return true;
        }
    • resolveArgument()方法

      • 代码如下
        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
        @Override
        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 + "]");
        }

        // 根据参数名获取参数值
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
        if (namedValueInfo.defaultValue != null) {
        arg = resolveStringValue(namedValueInfo.defaultValue);
        }
        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 {
        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;
        }

2.1.1 RequestParamMethodArgumentResolver

2.1.1 RequestAttributeMethodArgumentResolver

3.1 总结

  • HandlerMethodArgumentResolver 的方法参数绑定处理是针对于不同的方法参数有专门的ArgumentResolver 专人做专事,专业

4.1 参考

官方文档: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html