【Spring MVC】MVC请求的处理过程一览-HandlerMethodArgumentResolver概述以及分类
1 前言
上节我们看了 【Spring MVC】MVC请求的处理过程一览-HandlerMethod 的处理请求的过程,其实就是先解析参数,然后调用你的具体业务逻辑,最后处理结果。解析参数的核心类就是我们本节要看的 HandlerMethodArgumentResolver。
2 HandlerMethodArgumentResolver
我们先回顾下解析参数的入口如下,就是调用 InvocableHandlerMethod 内部的 resolvers 属性进行参数解析的:
//InvocableHandlerMethod#getMethodArgumentValues args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);


HandlerMethodArgumentResolverComposite 可以看到它是个复合型的类,本身没有解析的能力,而是内部有一堆解析器,那我们先看看它内部解析的来源,方便更好的理解。
2.1 解析器初始化由来
初始化其实我们看过,在这一节【Spring MVC】MVC请求的处理过程一览-RequestMappingHandlerAdapter、ModelAndViewContainer 的这里,截图如下:

也就是 RequestMappingHandlerAdapter 的 afterPropertiesSet 方法里,但是你会发现他是给 RequestMappingHandlerAdapter 内部的 argumentResolvers 属性赋值的,那他是什么时候给到 HandlerMethod 的,我就不一点点带你们看了哈,我这里直接画了个图:

2.2 解析器概述
每个 Resolver 对应一种类型的参数,它的实现类非常多,有一个实现类比较特殊,那就是HandlerMethodArgumentResolverComposite,就是我们上边看到的,它不具体解析参数,而是可以将多个别的解析器包含在其中,解析时调用其所包含的解析器具体解析参数。
下面先来看一下 HandlerMethodArgumentResolver 接口的定义:
public interface HandlerMethodArgumentResolver { // 判断是否支持该参数 boolean supportsParameter(MethodParameter parameter); /** * 解析参数 返回值就是解析后的值 * @param parameter 方法参数 * @param mavContainer 传递上下文的容器 * @param webRequest 请求 * @param binderFactory * @return * @throws Exception */ Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
非常简单,只有两个方法,一个用于判断是否可以解析传入的参数,另一个就是用于实际解析参数。
HandlerMethodArgumentResolver 实现类一般有两种命名方式,一种是 XXXMethodArgumentResolver,另一种是 XXXMethodProcessor。前者表示一个参数解析器,后者除了可以解析参数外还可以处理相应类型的返回值,也就是同时还是后面要讲到的HandlerMethodReturnValueHandle。其中的XXX表示用于解析的参数的类型。另外,还有个Adapter,它也不是直接解析参数的,而是用来兼容 WebArgumentResolver 类型的参数解析器的适配器。
2.3 解析器分类
我们来看下这些解析器以及他都是解析哪种类型的参数:
(1)注解型解析器
@RequestParam:RequestParamMethodArgumentResolver、RequestParamMapMethodArgumentResolver
@PathVariable:PathVariableMethodArgumentResolver、PathVariableMapMethodArgumentResolver
@MatrixVariable:MatrixVariableMethodArgumentResolver、MatrixVariableMapMethodArgumentResolver
@ModelAttribute或者无注解的非基本类型的:ServletModelAttributeMethodProcessor
@RequestBody:RequestResponseBodyMethodProcessor
@RequestHeader:RequestHeaderMethodArgumentResolver、RequestHeaderMapMethodArgumentResolver
@CookieValue:ServletCookieValueMethodArgumentResolver
@Value:ExpressionValueMethodArgumentResolver
@SessionAttribute:SessionAttributeMethodArgumentResolver
@RequestAttribute:RequestAttributeMethodArgumentResolver
(2)基础型解析器
比如我们常常会在参数中写到的:HttpServletRequest、HttpServletResponse 下边这两个解析器注入的:
ServletRequest、WebRequest、MultipartRequest、HttpMethod、TimeZone等:ServletRequestMethodArgumentResolver
ServletResponse、OutputStream、Writer:ServletResponseMethodArgumentResolver
其他:HttpEntityMethodProcessor、RedirectAttributesMethodArgumentResolver、ModelMethodProcessor、MapMethodProcessor、ErrorsMethodArgumentResolver、SessionStatusMethodArgumentResolver、UriComponentsBuilderMethodArgumentResolver
(3)全局型解析器
PrincipalMethodArgumentResolver、RequestParamMethodArgumentResolver、ServletModelAttributeMethodProcessor
现在我们一般都用的注解型的,我们接下来看下常用的注解解析器的定义以及 supportsParameter 的定义。
2.3.1 @RequestParam
@RequestParam 类型的,默认情况下是必传的:
// @RequestParam 定义 public @interface RequestParam { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; String defaultValue() default ValueConstants.DEFAULT_NONE; }
涉及的解析器:RequestParamMethodArgumentResolver、RequestParamMapMethodArgumentResolver
// RequestParamMethodArgumentResolver // (1)参数有 @RequestParam 注解的非 Map类型的参数 (2)参数有@RequestParam 注解的并且指定了 name属性值的 // 如果参数类型是 map 的如果指定了 @RequestParam的name的话 也是这个解析器但是默认情况下会报异常 // Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'java.util.Map': no matching editors or conversion strategy found // @RequestParam("name2") String nickName 这个按name2注入 @GetMapping("/test2") public void test2(@RequestParam String name, @RequestParam("name2") String nickName, @RequestParam("dd") Map<String, Object> map) {} // RequestParamMethodArgumentResolver#supportsParameter() if (parameter.hasParameterAnnotation(RequestParam.class)) { if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); return (requestParam != null && StringUtils.hasText(requestParam.name())); } else { return true; } }
再看下另一个RequestParamMapMethodArgumentResolver:
// RequestParamMapMethodArgumentResolver // 刚好是上边情况的补充 // 参数有 @RequestParam 注解的并且是 Map类型的参数并且没有指定注解的 name 值 @GetMapping("/test2") public void test2(@RequestParam Map<String, Object> map) {} // RequestParamMapMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class); return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(requestParam.name())); }
2.3.2 @PathVariable
@PathVariable 类型的,默认情况下是必传的:
// @PathVariable 定义 public @interface PathVariable { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; }
涉及的解析器:PathVariableMethodArgumentResolver、PathVariableMapMethodArgumentResolver
// PathVariableMethodArgumentResolver // (1)参数有 @RequestParam 注解并且不是 map 类型的 (2)参数有 @RequestParam 注解并且指定了name属性值的 @GetMapping("/test2/{id}") public void test2(@PathVariable("id") String id) {} public void test2(@PathVariable String id) {} // PathVariableMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { // 参数不含有 @PathVariable 直接略过 if (!parameter.hasParameterAnnotation(PathVariable.class)) { return false; } // 有 @PathVariable 注解参数类型是 map 的话指定了 name的值 if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class); return (pathVariable != null && StringUtils.hasText(pathVariable.value())); } // 有 @PathVariable 注解参数类型不是 map 的 return true; }
// PathVariableMapMethodArgumentResolver // 刚好是上边的补充 // 参数有 @PathVariable 注解的并且参数类型是 Map 的并且没有指定注解的 name 值 @GetMapping("/test2/{key}/{value}") public void test2(@PathVariable Map<String, Object> map) {} // PathVariableMapMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); // 有 @PathVariable 注解并且类型是 Map 的并且没有指定 name属性的 return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(ann.value())); }
2.3.3 @MatrixVariable
@MatrixVariable 类型的,默认情况下是必传的,这个注解说实话工作上没见到过,可能比较老的注解吧,我也是看了看别人的一些用法哈,这里简单客串下:
public @interface MatrixVariable { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; String pathVar() default ValueConstants.DEFAULT_NONE; boolean required() default true; String defaultValue() default ValueConstants.DEFAULT_NONE; }
涉及的解析器:MatrixVariableMethodArgumentResolver、MatrixVariableMapMethodArgumentResolver
我这里试了几个例子,他是属于路径参数,解析路径上;key=val;key2=val2类似这种形式的参数:
// 请求路径:testMatrixVariable/1111;haha=nidehaha/dd/222;hehe=nidehehe // 结果:haha=nidehaha, hehe=nidehehe @GetMapping("/testMatrixVariable/{var1}/dd/{var2}") public void testMatrixVariable(@MatrixVariable(pathVar = "var1", name = "haha") String haha, @MatrixVariable(pathVar = "var2", name = "hehe") String hehe) { log.info("haha={}, hehe={}", haha, hehe); } // 请求路径:testMatrixVariable/1111;haha=nidehaha;hehe=nidehehe // 结果:haha=nidehaha, hehe=nidehehe @GetMapping("/testMatrixVariable/{var1}") public void testMatrixVariable(@MatrixVariable(name = "haha") String haha, @MatrixVariable(name = "hehe") String hehe) { log.info("haha={}, hehe={}", haha, hehe); }
看下两个解析器的支持定义:
// MatrixVariableMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { // 参数没有 @MatrixVariable 注解直接返回 false if (!parameter.hasParameterAnnotation(MatrixVariable.class)) { return false; } // 有 @MatrixVariable 注解是Map类型的并且含有 name 属性值的 if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) { MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class); return (matrixVariable != null && StringUtils.hasText(matrixVariable.name())); } // 有 @MatrixVariable 注解并且不是 Map类型的参数 return true; }
// MatrixVariableMapMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { // 含有 @MatrixVariable 注解并且是 Map类型的参数并且没有 name 属性值 MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class); return (matrixVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) && !StringUtils.hasText(matrixVariable.name())); }
2.3.4 @ModelAttribute
我们看下注解的定义:
public @interface ModelAttribute { String value() default ""; @AliasFor("value") String name() default ""; boolean binding() default true; }
// ServletModelAttributeMethodProcessor 继承了 ModelAttributeMethodProcessor public boolean supportsParameter(MethodParameter parameter) { // 含有 @ModelAttribute注解的参数 annotationNotRequired默认是 false 也就是没有 @ModelAttribute 注解的情况下不是基本类型的 不是字符串等类型的 return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); }
2.3.5 @RequestBody
我们常用的 POST 请求方式的参数解析器:
public @interface RequestBody { boolean required() default true; } // RequestResponseBodyMethodProcessor#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); }
2.3.6 @RequestHeader
public @interface RequestHeader { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; // 默认情况下必传的 boolean required() default true; String defaultValue() default ValueConstants.DEFAULT_NONE; } // RequestHeaderMapMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { // 含有 @RequestHeader 并且参数类型是 Map return (parameter.hasParameterAnnotation(RequestHeader.class) && Map.class.isAssignableFrom(parameter.getParameterType())); }
2.3.7 @CookieValue
public @interface CookieValue { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; String defaultValue() default ValueConstants.DEFAULT_NONE; } // ServletCookieValueMethodArgumentResolver继承了AbstractCookieValueMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(CookieValue.class); }
2.3.8 @Value
public @interface Value { String value(); } // ExpressionValueMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(Value.class); }
2.3.9 @SessionAttribute
public @interface SessionAttribute { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; } // SessionAttributeMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(SessionAttribute.class); }
2.3.10 @RequestAttribute
public @interface RequestAttribute { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; } // RequestAttributeMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestAttribute.class); }
我们顺便看下两个平时帮我们填充 request、response的两个解析器的支持参数定义。
2.3.11 ServletRequestMethodArgumentResolver
// ServletRequestMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (WebRequest.class.isAssignableFrom(paramType) || ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType) || HttpSession.class.isAssignableFrom(paramType) || (pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) || (Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) || InputStream.class.isAssignableFrom(paramType) || Reader.class.isAssignableFrom(paramType) || HttpMethod.class == paramType || Locale.class == paramType || TimeZone.class == paramType || ZoneId.class == paramType); }
2.3.12 ServletResponseMethodArgumentResolver
// ServletResponseMethodArgumentResolver#supportsParameter() public boolean supportsParameter(MethodParameter parameter) { Class<?> paramType = parameter.getParameterType(); return (ServletResponse.class.isAssignableFrom(paramType) || OutputStream.class.isAssignableFrom(paramType) || Writer.class.isAssignableFrom(paramType)); }
3 小结
好啦,本节我们主要是看下参数解析器的一些概述、默认参数解析器的由来以及解析器的类型、支持的方法定义,下节我们看下这些解析器解析参数的过程,有理解不对的地方还请指正哈。

浙公网安备 33010602011771号