【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  小结

好啦,本节我们主要是看下参数解析器的一些概述、默认参数解析器的由来以及解析器的类型、支持的方法定义,下节我们看下这些解析器解析参数的过程,有理解不对的地方还请指正哈。 

posted @ 2025-06-21 15:15  酷酷-  阅读(30)  评论(0)    收藏  举报