目录

今日说码

点滴记录中国代码进程

X

简单分析 SpringMVC 参数解析器

概述

之前在实现支持多@RequestBody的自定义注解 - 今日说码 (96xl.top)的时候了解了 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() 方法取出参数返回。

标题:简单分析 SpringMVC 参数解析器
作者:96XL
地址:https://solo.96xl.top/articles/2023/02/22/1677057558804.html