本文最后更新于 2025-04-28,文章内容可能已经过时。

概述

之前在实现支持多 @RequestBody 的自定义注解 | 今日说码​的时候了解了 SpringMVC 的参数解析器以及部分实现类,这里记录下学习过程。

参数解析器概览

​HandlerMethodArgumentResolver​ 是参数解析器的接口,所有参数解析器都要实现该接口,它有非常多的实现类:

image.png

为了理解方便,我们可以将这些参数解析器分为四大类:

  • ​xxxMethodArgumentResolver​:这就是一个普通的参数解析器。

  • ​xxxMethodProcessor​:不仅可以当作参数解析器,还可以处理对应类型的返回值。

  • ​xxxAdapter​:这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。

  • ​HandlerMethodArgumentResolverComposite​:这是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器。

其中最重要的就是前两种。

参数解析器介绍

  • ​MapMethodProcessor​:这个用来处理 Map/ModelMap 类型的参数,解析完成后返回 model。

  • ​PathVariableMethodArgumentResolver​:这个用来处理使用了@PathVariable​ 注解并且参数类型不为 Map 的参数,参数类型为 Map 则使用PathVariableMapMethodArgumentResolver​ 来处理。

  • ​PathVariableMapMethodArgumentResolver​:见上。

  • ​ErrorsMethodArgumentResolver​:这个用来处理 Error 参数,例如我们做参数校验时的 BindingResult。

  • ​AbstractNamedValueMethodArgumentResolver​:这个用来处理 key/value 类型的参数,如请求头参数、使用了@PathVariable​ 注解的参数以及 Cookie 等。

  • ​RequestHeaderMethodArgumentResolver​:这个用来处理使用了@RequestHeader​ 注解,并且参数类型不是 Map 的参数(参数类型是 Map 的使用RequestHeaderMapMethodArgumentResolver​)。

  • ​RequestHeaderMapMethodArgumentResolver​:见上。

  • ​RequestAttributeMethodArgumentResolver​:这个用来处理使用了@RequestAttribute​ 注解的参数。

  • ​RequestParamMethodArgumentResolver​:这个功能就比较广了,使用了@RequestParam​ 注解的参数、文件上传的类型 MultipartFile、或者一些没有使用任何注解的基本类型(Long、Integer)以及 String 等,都使用该参数解析器处理。需要注意的是,如果@RequestParam​ 注解的参数类型是 Map,则该注解必须有 name 值,否则解析将由RequestParamMapMethodArgumentResolver​ 完成。

  • ​RequestParamMapMethodArgumentResolver​:见上。

  • ​AbstractCookieValueMethodArgumentResolver​:这个是一个父类,处理使用了@CookieValue​ 注解的参数。

  • ​ServletCookieValueMethodArgumentResolver​:这个处理使用了@CookieValue​ 注解的参数。

  • ​SessionAttributeMethodArgumentResolver​:这个用来处理使用了@SessionAttribute​ 注解的参数。

  • ​MatrixVariableMethodArgumentResolver​:这个处理使用了@MatrixVariable​ 注解并且参数类型不是 Map 的参数,如果参数类型是 Map,则使用MatrixVariableMapMethodArgumentResolver​ 来处理。

  • ​MatrixVariableMapMethodArgumentResolver​:见上。

  • ​ExpressionValueMethodArgumentResolver​:这个用来处理使用了@Value​ 注解的参数。

  • ​ServletResponseMethodArgumentResolver​:这个用来处理 ServletResponse、OutputStream 以及 Writer 类型的参数。

  • ​ModelMethodProcessor​:这个用来处理 Model 类型参数,并返回 model。

  • ​ModelAttributeMethodProcessor​:这个用来处理使用了@ModelAttribute​ 注解的参数。

  • ​ServletModelAttributeMethodProcessor​:这个用来处理使用了@ModelAttribute​ 注解的参数。

  • ​SessionStatusMethodArgumentResolver​:这个用来处理 SessionStatus 类型的参数。

  • ​PrincipalMethodArgumentResolver​:这个用来处理 Principal 类型的参数。

  • ​ContinuationHandlerMethodArgumentResolver​:参数类型为 kotlin.coroutines.Continuation 的无操作解析器。

  • ​AbstractMessageConverterMethodArgumentResolver​:这是一个父类,当使用 HttpMessageConverter 解析 requestbody 类型参数时,相关的处理类都会继承自它。

  • ​RequestPartMethodArgumentResolver​:这个用来处理使用了@RequestPart​ 注解、MultipartFile 以及 Part 类型的参数。

  • ​AbstractMessageConverterMethodProcessor​:这是一个工具类,不承担参数解析任务。

  • ​RequestResponseBodyMethodProcessor​:这个用来处理添加了@RequestBody​ 注解的参数。

  • ​HttpEntityMethodProcessor​:这个用来处理 HttpEntity 和 RequestEntity 类型的参数。

  • ​AbstractWebArgumentResolverAdapter​:这种不做参数解析,仅仅用来作为 WebArgumentResolver 类型的参数解析器的适配器。

  • ​ServletWebArgumentResolverAdapter​:这个给父类提供 request。

  • ​UriComponentsBuilderMethodArgumentResolver​:这个用来处理 UriComponentsBuilder 类型的参数。

  • ​ServletRequestMethodArgumentResolver​:这个用来处理 WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、InputStream、Reader、HttpMethod、Locale、TimeZone、ZoneId 类型的参数。

  • ​HandlerMethodArgumentResolverComposite​:这是一个组合解析器,它是一个代理,具体代理其他干活的那些参数解析器。

  • ​RedirectAttributesMethodArgumentResolver​:这个用来处理 RedirectAttributes 类型的参数。

AbstractNamedValueMethodArgumentResolver

​AbstractNamedValueMethodArgumentResolver​ 是一个抽象类,一些键值对类型的参数解析器都是通过继承它实现的,它里边定义了很多这些键值对类型参数解析器的公共操作。其中也是应用了很多模版模式,例如它没有实现 supportsParameter()​ 方法,该方法的具体实现在不同的子类中,不过它实现了 resolveArgument()​ 方法:

@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    NamedValueInfo namedValueInfo = this.getNamedValueInfo(parameter);
    MethodParameter nestedParameter = parameter.nestedIfOptional();
    Object resolvedName = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
    if (resolvedName == null) {
        throw new IllegalArgumentException("Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    } else {
        Object arg = this.resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            if (namedValueInfo.defaultValue != null) {
                arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
            } else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                this.handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }

            arg = this.handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        } else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = this.resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            WebDataBinder binder = binderFactory.createBinder(webRequest, (Object)null, namedValueInfo.name);

            try {
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            } catch (ConversionNotSupportedException var11) {
                throw new MethodArgumentConversionNotSupportedException(arg, var11.getRequiredType(), namedValueInfo.name, parameter, var11.getCause());
            } catch (TypeMismatchException var12) {
                throw new MethodArgumentTypeMismatchException(arg, var12.getRequiredType(), namedValueInfo.name, parameter, var12.getCause());
            }

            if (arg == null && namedValueInfo.defaultValue == null && namedValueInfo.required && !nestedParameter.isOptional()) {
                this.handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
            }
        }

        this.handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
        return arg;
    }
}

根据代码可以看到,resolveArgument()​ 方法的流程如下:

  1. 首先根据当前请求获取一个 NamedValueInfo 对象,这个对象中保存了参数的三个属性:参数名、参数是否必须以及参数默认值。具体的获取过程就是先去缓存中拿,缓存中如果有,就直接返回,缓存中如果没有,则调用 createNamedValueInfo()​ 方法去创建,将创建结果缓存起来并返回。createNamedValueInfo()​ 方法是一个模版方法,具体的实现在子类中。

  2. 接下来处理 Optional 类型参数。

  3. ​resolveEmbeddedValuesAndExpressions()​ 方法是为了处理注解中使用了 SpEL 表达式的情况,例如如下接口:

    @GetMapping("/hello2") 
    public void hello2(@RequestParam(value = "${aa.bb}") String name) { 
        System.out.println("name = " + name); 
    }
    

    参数名使用了表达式,那么 resolveEmbeddedValuesAndExpressions()​ 方法的目的就是解析出表达式的值,如果没用到表达式,那么该方法会将原参数原封不动返回。

  4. 接下来调用 resolveName()​ 方法解析出参数的具体值,这个方法也是一个模版方法,具体的实现在子类中。

  5. 如果获取到的参数值为 null,先去看注解中有没有默认值,然后再去看参数值是否是必须的,如果是,则抛异常出来,否则就设置为 null 即可。

  6. 如果解析出来的参数值为空字符串 "",则也去 resolveEmbeddedValuesAndExpressions()​ 方法中走一遭。

  7. 最后则是 WebDataBinder 的处理,解决一些全局参数的问题。

在这个流程中,可以看到主要有两个方法是在子类中实现的,分别是 createNamedValueInfo()​ 方法和 resolveName()​ 方法,再加上 supportsParameter()​ 方法,子类中一共有三个方法需要实现。下面以 RequestParamMethodArgumentResolver​ 为例,来看下这三个方法。

RequestParamMethodArgumentResolver

supportsParameter

public boolean supportsParameter(MethodParameter parameter) {
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        if (!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            return true;
        } else {
            RequestParam requestParam = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
            return requestParam != null && StringUtils.hasText(requestParam.name());
        }
    } else if (parameter.hasParameterAnnotation(RequestPart.class)) {
        return false;
    } else {
        parameter = parameter.nestedIfOptional();
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        } else {
            return this.useDefaultResolution ? BeanUtils.isSimpleProperty(parameter.getNestedParameterType()) : false;
        }
    }
}

从 supportsParameter()​ 方法中可以非常方便的看出支持的参数类型:

  1. 首先参数如果有@RequestParam​ 注解的话,则分两种情况:参数类型如果是 Map,则@RequestParam​ 注解必须配置 name 属性,否则不支持;如果参数类型不是 Map,则支持。

  2. 参数如果含有@RequestPart​ 注解,则不支持。

  3. 检查下是不是文件上传请求,如果是,则支持。

  4. 如果前面都没能返回,则使用默认的解决方案,调用BeanUtils.isSimpleProperty()​ 方法判断是不是简单类型,主要就是 Void、枚举、字符串、数字、日期等等。

createNamedValueInfo

protected AbstractNamedValueMethodArgumentResolver.NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    RequestParam ann = (RequestParam)parameter.getParameterAnnotation(RequestParam.class);
    return ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo();
}

获取注解,读取注解中的属性,构造 RequestParamNamedValueInfo 对象返回。

resolveName

@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    HttpServletRequest servletRequest = (HttpServletRequest)request.getNativeRequest(HttpServletRequest.class);
    Object arg;
    if (servletRequest != null) {
        arg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (arg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return arg;
        }
    }

    arg = null;
    MultipartRequest multipartRequest = (MultipartRequest)request.getNativeRequest(MultipartRequest.class);
    if (multipartRequest != null) {
        List<MultipartFile> files = multipartRequest.getFiles(name);
        if (!files.isEmpty()) {
            arg = files.size() == 1 ? files.get(0) : files;
        }
    }

    if (arg == null) {
        String[] paramValues = request.getParameterValues(name);
        if (paramValues != null) {
            arg = paramValues.length == 1 ? paramValues[0] : paramValues;
        }
    }

    return arg;
}

根据代码可以看到,resolveName()​ 方法的逻辑比较简单:

  1. 前面两个 if 主要是为了处理文件上传请求。

  2. 如果不是文件上传请求,则调用request.getParameterValues()​ 方法取出参数返回。