精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读

Spring 版本:5.1.14.RELEASE

该系列其他文档请查看:《精尽 Spring MVC 源码分析 - 文章导读》

HandlerAdapter 组件

HandlerAdapter 组件,处理器的适配器。因为处理器 handler 的类型是 Object 类型,需要有一个调用者来实现 handler 是怎么被执行。Spring 中的处理器的实现多变,比如用户的处理器可以实现 Controller 接口或者 HttpRequestHandler 接口,也可以用 @RequestMapping 注解将方法作为一个处理器等,这就导致 Spring MVC 无法直接执行这个处理器。所以这里需要一个处理器适配器,由它去执行处理器

由于 HandlerMapping 组件涉及到的内容较多,考虑到内容的排版,所以将这部分内容拆分成了五个模块,依次进行分析:

HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

本文是接着《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》一文来分享 HandlerMethodArgumentResolver 组件。在 HandlerAdapter 执行处理器的过程中,具体的执行过程交由 ServletInvocableHandlerMethod 对象来完成,其中需要先通过 HandlerMethodArgumentResolver 参数解析器从请求中解析出方法的入参,然后再通过反射机制调用对应的方法。

回顾

先来回顾一下 ServletInvocableHandlerMethod 在哪里调用参数解析器的,可以回到 《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》InvocableHandlerMethod 小节下面的 getMethodArgumentValues 方法,如下:

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
        Object... providedArgs) throws Exception {
    // 获得方法的参数
    MethodParameter[] parameters = getMethodParameters();
    // 无参,返回空数组
    if (ObjectUtils.isEmpty(parameters)) {
        return EMPTY_ARGS;
    }
    // 将参数解析成对应的类型
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
        // 获得当前遍历的 MethodParameter 对象,并设置 parameterNameDiscoverer 到其中
        MethodParameter parameter = parameters[i];
        parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
        // <1> 先从 providedArgs 中获得参数。如果获得到,则进入下一个参数的解析,默认情况 providedArgs 不会传参
        args[i] = findProvidedArgument(parameter, providedArgs);
        if (args[i] != null) {
            continue;
        }
         // <2> 判断 resolvers 是否支持当前的参数解析
        if (!this.resolvers.supportsParameter(parameter)) {
            throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
        }
        try {
            // 执行解析,解析成功后,则进入下一个参数的解析
            args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
        }
        catch (Exception ex) {
            // Leave stack trace for later, exception may actually be resolved and handled...
            if (logger.isDebugEnabled()) {
                String exMsg = ex.getMessage();
                if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                    logger.debug(formatArgumentError(parameter, exMsg));
                }
            }
            throw ex;
        }
    }
    return args;
}
  • <2> 处,在获取到 Method 方法的所有参数对象,依次处理,根据 resolvers 判断是否支持该参数的处理,如果支持则进行参数转换

  • resolvers 为 HandlerMethodArgumentResolverComposite 组合对象,包含了许多的参数解析器

HandlerMethodArgumentResolver 接口

org.springframework.web.method.support.HandlerMethodArgumentResolver,方法参数解析器

public interface HandlerMethodArgumentResolver {
	/**
	 * 是否支持解析该参数
	 */
	boolean supportsParameter(MethodParameter parameter);
	/**
	 * 解析该参数
	 */
	@Nullable
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

类图

因为请求入参的场景非常多,所以 HandlerMethodArgumentResolver 的实现类也非常多,上面仅列出了部分实现类,本文也分析上面图中右侧常见的几种参数场景

HandlerMethodArgumentResolverComposite

org.springframework.web.method.support.HandlerMethodArgumentResolverComposite,实现 HandlerMethodArgumentResolver 接口,复合的 HandlerMethodArgumentResolver 实现类

构造方法

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
	/**
	 * HandlerMethodArgumentResolver 数组
	 */
	private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
	/**
	 * MethodParameter 与 HandlerMethodArgumentResolver 的映射,作为缓存
	 */
	private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
}
  • argumentResolvers:HandlerMethodArgumentResolver 数组。这就是 Composite 复合~
  • argumentResolverCache:MethodParameter 与 HandlerMethodArgumentResolver 的映射,作为缓存。因为,MethodParameter 是需要从 argumentResolvers 遍历到适合其的解析器,通过缓存后,无需再次重复遍历

《HandlerAdapter 组件(一)之 HandlerAdapter》RequestMappingHandlerAdapter小节的 getDefaultArgumentResolvers 方法中可以看到,默认的 argumentResolvers 有哪些 HandlerMethodArgumentResolver 实现类,注意这里是有顺序的添加哦

getArgumentResolver

getArgumentResolver(MethodParameter parameter) 方法,获得方法参数对应的 HandlerMethodArgumentResolver 对象,方法如下:

@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
    // 优先从 argumentResolverCache 缓存中,获得 parameter 对应的 HandlerMethodArgumentResolver 对象
    HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
    if (result == null) {
        // 获得不到,则遍历 argumentResolvers 数组,逐个判断是否支持。
        for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
            // 如果支持,则添加到 argumentResolverCache 缓存中,并返回
            if (resolver.supportsParameter(parameter)) {
                result = resolver;
                this.argumentResolverCache.put(parameter, result);
                break;
            }
        }
    }
    return result;
}

很简单,先从argumentResolverCache缓存中获取,没有获取到则遍历 argumentResolvers,如果支持该参数则该 HandlerMethodArgumentResolver 对象并缓存起来

注意,往 argumentResolvers 添加的顺序靠前,则优先判断是否支持该参数哦~

supportsParameter

实现 supportsParameter(MethodParameter parameter) 方法,如果能获得到对应的 HandlerMethodArgumentResolver 参数处理器,则说明支持处理该参数,方法如下:

@Override
public boolean supportsParameter(MethodParameter parameter) {
    return getArgumentResolver(parameter) != null;
}

resolveArgument

实现 resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 方法,解析出指定参数的值,方法如下:

@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    // 获取参数解析器
    HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
    if (resolver == null) {
        throw new IllegalArgumentException("Unsupported parameter type [" +
                parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
    }
    /**
     * 进行解析
     *
     * 基于 @RequestParam 注解
     * {@link org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#resolveArgument}
     * 基于 @PathVariable 注解
     * {@link org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver#resolveArgument}
     */
    return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}

很简单,获取到该方法参数对应的 HandlerMethodArgumentResolver 参数处理器,然后调用其 resolveArgument 执行解析

AbstractNamedValueMethodArgumentResolver

org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver,实现 ValueMethodArgumentResolver 接口,基于名字获取值的HandlerMethodArgumentResolver 抽象基类。例如说,@RequestParam(value = "username") 注解的参数,就是从请求中获得 username 对应的参数值。😈 明白了么?

AbstractNamedValueMethodArgumentResolver 的子类也有挺多了,我们仅分析它的两个子类,如上面类图的下面两个:

  • RequestParamMethodArgumentResolver:基于 @RequestParam 注解( 也可不加该注解的请求参数 )的方法参数,详情见下文
  • PathVariableMethodArgumentResolver ,基于 @PathVariable 注解的方法参数,详情见下文

构造方法

public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Nullable
	private final ConfigurableBeanFactory configurableBeanFactory;

	@Nullable
	private final BeanExpressionContext expressionContext;
	/**
	 * MethodParameter 和 NamedValueInfo 的映射,作为缓存
	 */
	private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
}

NamedValueInfo 内部类

AbstractNamedValueMethodArgumentResolver 的静态内部类,代码如下:

protected static class NamedValueInfo {
    /**
     * 名字
     */
    private final String name;

    /**
     * 是否必填
     */
    private final boolean required;

    /**
     * 默认值
     */
    @Nullable
    private final String defaultValue;

    public NamedValueInfo(String name, boolean required, @Nullable String defaultValue) {
        this.name = name;
        this.required = required;
        this.defaultValue = defaultValue;
    }
}

getNamedValueInfo

private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
    // <1> 从 namedValueInfoCache 缓存中,获得 NamedValueInfo 对象
    NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
    if (namedValueInfo == null) {
        // <2> 获得不到,则创建 namedValueInfo 对象。这是一个抽象方法,子类来实现
        namedValueInfo = createNamedValueInfo(parameter);
         // <3> 更新 namedValueInfo 对象
        namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
        // <4> 添加到 namedValueInfoCache 缓存中
        this.namedValueInfoCache.put(parameter, namedValueInfo);
    }
    return namedValueInfo;
}
  1. namedValueInfoCache 缓存中,获得 NamedValueInfo 对象,获取到则直接返回

  2. 获得不到,则调用 createNamedValueInfo(MethodParameter parameter) 方法,创建 NamedValueInfo 对象。这是一个抽象方法,交由子类来实现

  3. 调用 updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) 方法,更新 NamedValueInfo 对象,方法如下:

    private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
        String name = info.name;
        if (info.name.isEmpty()) {
            // 【注意!!!】如果 name 为空,则使用参数名
            name = parameter.getParameterName();
            if (name == null) {
                throw new IllegalArgumentException(
                        "Name for argument type [" + parameter.getNestedParameterType().getName() +
                        "] not available, and parameter name information not found in class file either.");
            }
        }
        // 获得默认值
        String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
        // 创建 NamedValueInfo 对象
        return new NamedValueInfo(name, info.required, defaultValue);
    }
    

    如果名称为空,则取参数名,获取默认值,创建一个新的 NamedValueInfo 对象返回

  4. 添加到 namedValueInfoCache 缓存中

  5. 返回该 NamedValueInfo 对象

resolveArgument

resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) 方法,从请求中解析出指定参数的值

@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

    // <1> 获得方法参数对应的 NamedValueInfo 对象。
    NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
    // <2> 如果 parameter 是内嵌类型(Optional 类型)的,则获取内嵌的参数。否则,还是使用 parameter 自身
    MethodParameter nestedParameter = parameter.nestedIfOptional();

    // <3> 如果 name 是占位符,则进行解析成对应的值
    Object resolvedName = resolveStringValue(namedValueInfo.name);
    if (resolvedName == null) {
        // 如果解析不到,则抛出 IllegalArgumentException 异常
        throw new IllegalArgumentException(
                "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
    }

    // <4> 解析 name 对应的值
    Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
    // <5> 如果 arg 不存在,则使用默认值
    if (arg == null) {
        // <5.1> 使用默认值
        if (namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }
        // <5.2> 如果是必填,则处理参数缺失的情况
        else if (namedValueInfo.required && !nestedParameter.isOptional()) {
            handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
        }
        // <5.3> 处理空值的情况
        arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
    }
    // <6> 如果 arg 为空串,则使用默认值
    else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
        arg = resolveStringValue(namedValueInfo.defaultValue);
    }

    // <7> 数据绑定相关
    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());

        }
    }

    // <8> 处理解析的值
    handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

    return arg;
}
  1. 调用 getNamedValueInfo(MethodParameter parameter) 方法,获得方法参数对应的 NamedValueInfo 对象

  2. 如果 parameter 是内嵌类型(Optional 类型)的,则获取内嵌的参数。否则,还是使用 parameter 自身。一般情况下,parameter 参数,我们不太会使用 Optional 类型。可以暂时忽略

  3. 调用 resolveStringValue(String value) 方法,如果 name 是占位符,则进行解析成对应的值,方法如下:

    @Nullable
    private Object resolveStringValue(String value) {
        // 如果 configurableBeanFactory 为空,则不进行解析
        if (this.configurableBeanFactory == null) {
            return value;
        }
        // 获得占位符对应的值
        String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
        // 获取表达式处理器对象
        BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
        if (exprResolver == null || this.expressionContext == null) {
            return value;
        }
        // 计算表达式
        return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
    }
    

    这种用法非常小众,从来没用过。示例如下:

    // Controller.java
    
    @RequestMapping("/hello3")
    public String hello3(@RequestParam(value = "${server.port}") String name) {
        return "666";
    }
    
    // application.properties
    server.port=8012
    

    此时,就可以发送 GET /hello3?8012=xxx 请求

  4. 【重点】调用 resolveName(String name, MethodParameter parameter, NativeWebRequest request) 抽象方法,解析参数名 name 对应的值,交由子类去实现

  5. 如果上面解析出来的参数值 argnull ,则使用默认值

    1. 如果默认值非空,则调用 resolveStringValue(defaultValue) 方法,解析默认值

    2. 如果是必填,则调用 handleMissingValue(handleMissingValue) 方法,处理参数缺失的情况调用,也就是抛出指定的异常

    3. 调用 handleNullValue(String name, Object value, Class<?> paramType) 方法,处理 null 值的情况,方法如下:

      @Nullable
      private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
          if (value == null) {
              if (Boolean.TYPE.equals(paramType)) {
                  return Boolean.FALSE;
              } else if (paramType.isPrimitive()) { // 如果是基本类型则不能为 null
                  throw new IllegalStateException("Optional " + paramType.getSimpleName() + " parameter '" + name +
                          "' is present but cannot be translated into a null value due to being declared as a " +
                          "primitive type. Consider declaring it as object wrapper for the corresponding primitive type.");
              }
          }
          return value;
      }
      
  6. 否则,如果 arg为空字符串,并且存在默认值,则和上面的 5.1 相同处理方式

  7. 数据绑定相关,暂时忽略

  8. 调用 handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) 方法,解析参数值的后置处理,空方法,子类可以覆盖,子类 PathVariableMethodArgumentResolver 会重写该方法

代码有点长,不过逻辑不难理解

RequestParamMethodArgumentResolver

org.springframework.web.method.annotation.RequestParamMethodArgumentResolver,实现 UriComponentsContributor 接口,继承 AbstractNamedValueMethodArgumentResolver 抽象类,参数解析器 HandlerMethodArgumentResolver 的实现类,处理普通的请求参数

构造方法

public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
		implements UriComponentsContributor {
	private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
	/**
	 * 是否使用默认解决
	 *
	 * 这个变量有点绕,见 {@link #supportsParameter(MethodParameter)} 方法
	 */
	private final boolean useDefaultResolution;

	public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
		this.useDefaultResolution = useDefaultResolution;
	}

	public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
			boolean useDefaultResolution) {
		super(beanFactory);
		this.useDefaultResolution = useDefaultResolution;
	}
}

supportsParameter

实现 supportsParameter(MethodParameter parameter) 方法,判断是否支持处理该方法入参,方法如下:

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // <3> 有 @RequestParam 注解的情况
    if (parameter.hasParameterAnnotation(RequestParam.class)) {
        // <3.1> 如果是 Map 类型,则 @RequestParam 注解必须要有 name 属性
        if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
            RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
            return (requestParam != null && StringUtils.hasText(requestParam.name()));
        }
        else {
            // <3.2> 否则返回 true
            return true;
        }
    }
    else {
        // 如果有 @RequestPart 注解,返回 false 。即 @RequestPart 的优先级 > @RequestParam
        if (parameter.hasParameterAnnotation(RequestPart.class)) {
            return false;
        }
        // 获得参数,如果存在内嵌的情况
        parameter = parameter.nestedIfOptional();
        // <1> 如果 Multipart 参数。则返回 true ,表示支持
        if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
            return true;
        }
        // <2> 如果开启 useDefaultResolution 功能,则判断是否为普通类型
        else if (this.useDefaultResolution) {
            return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
        }
        // 其它,不支持
        else {
            return false;
        }
    }
}
  1. 如果 Multipart 参数。则返回 true ,表示支持调用 MultipartResolutionDelegate#isMultipartArgument(parameter) 方法,如果 Multipart 参数。则返回 true ,表示支持。代码如下:

    public static boolean isMultipartArgument(MethodParameter parameter) {
        Class<?> paramType = parameter.getNestedParameterType();
        return (MultipartFile.class == paramType ||
                isMultipartFileCollection(parameter) || isMultipartFileArray(parameter) ||
                (Part.class == paramType || isPartCollection(parameter) || isPartArray(parameter)));
    }
    

    上传文件相关类型

  2. 如果开启 useDefaultResolution 功能,则调用 BeanUtils#isSimpleProperty(Class<?> clazz) 方法,判断是否为普通类型,代码如下:

    public static boolean isSimpleProperty(Class<?> type) {
        Assert.notNull(type, "'type' must not be null");
        return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
    }
    public static boolean isSimpleValueType(Class<?> type) {
        return (type != void.class && type != Void.class &&
                (ClassUtils.isPrimitiveOrWrapper(type) ||
                Enum.class.isAssignableFrom(type) ||
                CharSequence.class.isAssignableFrom(type) ||
                Number.class.isAssignableFrom(type) ||
                Date.class.isAssignableFrom(type) ||
                URI.class == type ||
                URL.class == type ||
                Locale.class == type ||
                Class.class == type));
    }
    

    那么 useDefaultResolution 到底是怎么被赋值的呢?回到 RequestMappingHandlerAdapter 的 getDefaultArgumentResolvers() 的方法,精简代码如下:

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
    
        // Annotation-based argument resolution
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
        
        // ... 省略许多 HandlerMethodArgumentResolver 的添加
        
        // Catch-all
        resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
        resolvers.add(new ServletModelAttributeMethodProcessor(true));
    
        return resolvers;
    }
    

    我们可以看到有两个 RequestParamMethodArgumentResolver 对象,前者 useDefaultResolutionfalse ,后者为 useDefaultResolutiontrue 。什么意思呢?优先将待有 @RequestParam 注解的请求参数给第一个 RequestParamMethodArgumentResolver 对象;其次,给中间省略的一大片参数解析器试试能不能解析;最后,使用第二个 RequestParamMethodArgumentResolver 兜底,处理剩余的情况。

  3. 如果该方法参数有 @RequestParam 注解的情况

    1. 如果是 Map 类型,则 @RequestParam 注解必须要有 name 属性,是不是感觉有几分灵异?答案在下面的 RequestParamMapMethodArgumentResolver 中揭晓
    2. 否则,返回 true

createNamedValueInfo

实现父类的 createNamedValueInfo(MethodParameter parameter) 方法,创建 NamedValueInfo 对象,方法如下:

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

private static class RequestParamNamedValueInfo extends NamedValueInfo {

    public RequestParamNamedValueInfo() {
        super("", false, ValueConstants.DEFAULT_NONE);
    }

    public RequestParamNamedValueInfo(RequestParam annotation) {
        super(annotation.name(), annotation.required(), annotation.defaultValue());
    }
}
  1. 如果方法参数有 @RequestParam 注解,则根据注解创建一个 RequestParamNamedValueInfo 对象,获取注解中的 namerequired defaultValue配置

  2. 否则,就创建一个空的 RequestParamNamedValueInfo 对象,三个属性分别为,空字符串falseValueConstants.DEFAULT_NONE

    上面的 getNamedValueInfo 方法中讲述到,name空字符串 没有关系,会获取方法的参数名

    说明:通过反射获取方法的参数名,我们只能获取到 arg0,arg1 的名称,因为jdk8之后这些变量名称没有被编译到class文件中,编译时需要指定-parameters选项,方法的参数名才会记录到class文件中,运行时我们就可以通过反射机制获取到,所以我们最好还是用 @RequestParam 注解来标注

    ValueConstants.DEFAULT_NONE 则会设置为 null

resolveName

实现 #resolveName(String name, MethodParameter parameter, NativeWebRequest request) 方法,获得参数的值,方法如下:

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    // 情况一,HttpServletRequest 情况下的 MultipartFile 和 Part 的情况
    HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

    if (servletRequest != null) {
        Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
        if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
            return mpArg;
        }
    }

    // 情况二,MultipartHttpServletRequest 情况下的 MultipartFile 的情况
    Object arg = null;
    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;
}
  • 情况一、二,是处理参数类型为文件 org.springframework.web.multipart.MultipartFilejavax.servlet.http.Part 的参数的获取,例如我们常用到 MultipartFile 作为参数就是在这里处理的
  • 情况三,是处理普通参数的获取。就是我们常见的 String、Integer 之类的请求参数,直接从请求中获取参数值就好了

因为在《MultipartResolver 组件》中讲过了会对请求进行处理,包括解析出参数,解析成对应的 HttpServletRequest 对象

获得到参数值后,就可以准备开始通过反射调用对应的方法了

RequestParamMapMethodArgumentResolver

org.springframework.web.method.annotation.RequestParamMapMethodArgumentResolver,实现 HandlerMethodArgumentResolver 接口,用于处理带有 @RequestParam 注解,但是注解上没有 name 属性的 Map 类型的参数, HandlerMethodArgumentResolver 的实现类,代码如下:

public class RequestParamMapMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
		return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
				!StringUtils.hasText(requestParam.name()));
	}

	@Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);

		// MultiValueMap 类型的处理
		if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
			Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
			if (valueType == MultipartFile.class) { // MultipartFile 类型
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
			}
			else if (valueType == Part.class) { // Part 类型
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
					for (Part part : parts) {
						result.add(part.getName(), part);
					}
					return result;
				}
				return new LinkedMultiValueMap<>(0);
			}
			else {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					for (String value : values) {
						result.add(key, value);
					}
				});
				return result;
			}
		}
		// 普通 Map 类型的处理
		else {
			Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
			if (valueType == MultipartFile.class) { // MultipartFile 类型
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
			}
			else if (valueType == Part.class) { // Part 类型
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedHashMap<String, Part> result = new LinkedHashMap<>(parts.size());
					for (Part part : parts) {
						if (!result.containsKey(part.getName())) {
							result.put(part.getName(), part);
						}
					}
					return result;
				}
				return new LinkedHashMap<>(0);
			}
			else {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					if (values.length > 0) {
						result.put(key, values[0]);
					}
				});
				return result;
			}
		}
	}
}

上面没有仔细看,实际上是有点看不懂,不知道处理场景😈就举两个例子吧

  1. 对于 RequestParamMapMethodArgumentResolver 类,它的效果是,将所有参数添加到 Map 集合中,示例如下:

    // Controller.java
    
    @RequestMapping("/hello")
    public String hello4(@RequestParam Map<String, Object> map) {
        return "666";
    }
    

    发送请求 GET /hello?name=yyy&age=20nameage 参数,就会都添加到 map

  2. 对于 RequestParamMethodArgumentResolver 类,它的效果是,将指定名字的参数添加到 Map 集合中,示例如下:

    // Controller.java
    
    @RequestMapping("/hello")
    public String hello5(@RequestParam(name = "map") Map<String, Object> map) {
        return "666";
    }
    

    发送请求 GET /hello4?map={"name": "yyyy", age: 20}map 参数的元素则都会添加到方法参数 map 中。当然,要注意下,实际请求要 UrlEncode 编码下参数,所以实际请求是 GET /hello?map=%7b%22name%22%3a+%22yyyy%22%2c+age%3a+20%7d

PathVariableMethodArgumentResolver

org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver,实现 UriComponentsContributor 接口,继承 AbstractNamedValueMethodArgumentResolver 抽象类,处理路径参数

supportsParameter

实现 supportsParameter(MethodParameter parameter) 方法,判断是否支持处理该方法参数,代码如下:

@Override
public boolean supportsParameter(MethodParameter parameter) {
    // <1> 如果无 @PathVariable 注解
    if (!parameter.hasParameterAnnotation(PathVariable.class)) {
        return false;
    }
    // <2> Map 类型,有 @PathVariable 注解,但是有 name 属性
    if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
        PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
        return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
    }
    return true;
}
  1. 如果没有 @PathVariable 注解则直接返回 fasle,也就是说必须配置 @PathVariable 注解
  2. 如果还是 Map 类型,则需要 @PathVariable 注解有 name 属性,才返回 true,查看 org.springframework.web.servlet.mvc.method.annotation.PathVariableMapMethodArgumentResolver 就理解了,和上述的逻辑差不多
  3. 否则,直接返回 true

createNamedValueInfo

实现 createNamedValueInfo(MethodParameter parameter) 方法,方法如下:

@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    // 获得 @PathVariable 注解
    PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
    Assert.state(ann != null, "No PathVariable annotation");
    // 创建 PathVariableNamedValueInfo 对象
    return new PathVariableNamedValueInfo(ann);
}

private static class PathVariableNamedValueInfo extends NamedValueInfo {

    public PathVariableNamedValueInfo(PathVariable annotation) {
        super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);
    }
}

必须要有 @PathVariable 注解,没有的话抛出异常,然后根据注解创建 PathVariableNamedValueInfo 对象

resolveName

实现 resolveName(String name, MethodParameter parameter, NativeWebRequest request) 方法,从请求路径中获取方法参数的值,方法如下:

@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
    // 获得路径参数
    Map<String, String> uriTemplateVars = (Map<String, String>) request.
        getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    // 获得参数值
    return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

handleResolvedValue

重写 handleResolvedValue(Object arg, String name, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest request) 方法,添加获得的属性值到请求的 View.PATH_VARIABLES 属性种,方法如下:

@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
        @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
    // 获得 pathVars
    String key = View.PATH_VARIABLES;
    int scope = RequestAttributes.SCOPE_REQUEST;
    Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
    // 如果不存在 pathVars,则进行创建
    if (pathVars == null) {
        pathVars = new HashMap<>();
        request.setAttribute(key, pathVars, scope);
    }
     // 添加 name + arg 到 pathVars 中
    pathVars.put(name, arg);
}

具体用途还不清楚😈

总结

HandlerAdapter 执行 HandlerMethod 处理器的过程中,会将该处理器封装成 ServletInvocableHandlerMethod 对象,通过该对象来执行处理器。该对象通过反射机制调用对应的方法,在调用方法之前,借助 HandlerMethodArgumentResolver 参数解析器从请求中获取到对应的方法参数值,因为你无法确认哪个参数值对应哪个参数,所以需要先通过它从请求中解析出参数值,一一对应,然后才能调用该方法。

HandlerMethodArgumentResolver 参数解析器的实现类非常多,采用了组合模式来进行处理,如果有某一个参数解析器支持解析该方法参数,则使用它从请求体中获取到该方法参数的值,注意这里有一定的先后顺序,因为是通过 LinkedList 保存所有的实现类,排在前面的实现类则优先处理。

本文分析了我们常用的 @RequestParam@PathVariable 注解所对应的 HandlerMethodArgumentResolver 实现类,如下:

  • RequestParamMethodArgumentResolver:解析 @RequestParam 注解配置参数(名称、是否必须、默认值),根据注解配置从请求获取参数值
  • PathVariableMethodArgumentResolver:解析 @PathVariable 注解配置的(名称、是否必须),根据注解配置从请求路径中获取参数值

注意,关于方法参数为 Map 类型,应该如何配置,可以参考上面的 RequestParamMapMethodArgumentResolver 小节中的两个示例

关于其他的 HandlerMethodArgumentResolver 实现类,感兴趣的可以去看看

在接下来的《HandlerAdapter 组件(四)之 HandlerMethodReturnValueHandler》中讲到 RequestResponseBodyMethodProcessor 既是 HandlerMethodReturnValueHandler 实现类,也是 HandlerMethodArgumentResolver 实现类,用于处理器 @RequestBody 和 @ResponseBody 两个注解

参考文章:芋道源码《精尽 Spring MVC 源码分析》

posted @ 2020-12-18 17:33  月圆吖  阅读(195)  评论(0编辑  收藏