2023-03-14  阅读(5)
原文作者:lifullmoon 原文地址:https://www.cnblogs.com/lifullmoon

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;
    }

类图

202303142235055761.png

因为请求入参的场景非常多,所以 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 对象返回
  1. 添加到 namedValueInfoCache 缓存中
  2. 返回该 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` 请求
  1. 【重点】 调用 resolveName(String name, MethodParameter parameter, NativeWebRequest request) 抽象 方法,解析参数名 name 对应的值,交由子类去实现

  2. 如果上面解析出来的参数值 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;
            }
  1. 否则,如果 arg为空字符串,并且存在默认值,则和上面的 5.1 相同处理方式
  2. 数据绑定相关,暂时忽略
  3. 调用 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)));
        }
上传文件相关类型
  1. 如果开启 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 对象,前者 `useDefaultResolution` 为 `false` ,后者为 `useDefaultResolution` 为 `true` 。什么意思呢?优先将待有 `@RequestParam` 注解的请求参数给第一个 RequestParamMethodArgumentResolver 对象;其次,给中间省略的一大片参数解析器试试能不能解析;最后,使用第二个 RequestParamMethodArgumentResolver 兜底,处理剩余的情况。
  1. 如果该方法参数有 @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 对象,获取注解中的 namerequireddefaultValue配置

  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=20`,`name` 和 `age` 参数,就会都添加到 `map` 中
  1. 对于 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 两个注解


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文