zoukankan      html  css  js  c++  java
  • HandlerMethodArgumentResolver[1]-Controller入参封装

    参考:HandlerMethodArgumentResolver(一):Controller方法入参自动封装器(将参数parameter解析为值)【享学Spring MVC】 - 云+社区 - 腾讯云 (tencent.com)

    HandlerMethodArgumentResolver

    策略接口:用于在给定请求的上下文中将方法参数解析为参数值。简单的理解为:它负责处理你Handler方法里的所有入参:包括自动封装、自动赋值、校验等等。有了它才能会让Spring MVC处理入参显得那么高级、那么自动化。

    HandlerMethodArgumentResolver = HandlerMethod + Argument(参数) + Resolver(解析器)。 解释为:它是HandlerMethod方法的解析器,将HttpServletRequest(header + body 中的内容)解析为HandlerMethod方法的参数(method parameters)

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

    因为子类众多,将其分类进行说明

    1. 基于Name
    2. 数据类型是Map
    3. 固定参数类型
    4. 基于ContentType的消息转换器

    基于Name

    从URI(路径变量)、HttpServletRequest、HttpSession、Header、Cookie…等中根据名称key来获取值

    这类处理器所有的都是基于抽象类AbstractNamedValueMethodArgumentResolver来实现,它是最为重要的分支(分类)

    // 支持占位符${} 和 SpEl 表达式 #{}
    public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {
    
        @Nullable
        private final ConfigurableBeanFactory configurableBeanFactory;
    
        @Nullable
        private final BeanExpressionContext expressionContext;
    
        private final Map<MethodParameter, NamedValueInfo> namedValueInfoCache = new ConcurrentHashMap<>(256);
    
        public AbstractNamedValueMethodArgumentResolver() {
            this.configurableBeanFactory = null;
            this.expressionContext = null;
        }
    
        /**
         * beanFactory: 用于解析 ${} 和 #{} 的
         */
        public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory) {
            this.configurableBeanFactory = beanFactory;
            this.expressionContext =
                    (beanFactory != null ? new BeanExpressionContext(beanFactory, new RequestScope()) : null);
        }
    
    
        /**
         * 核心方法, final 的, 并不希望子类覆盖
         */
        @Override
        @Nullable
        public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
                NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
            // 获取参数(注解)信息
            NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
            // ? 获取 Optional 内的类型. emm, 如 Optional<List<User>>, 那么这里得到哪种类型呢
            MethodParameter nestedParameter = parameter.nestedIfOptional();
            // 注解标注的 name  属性解析(占位符${} 和 SpEL 表达式 #{} )
            Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
            if (resolvedName == null) {
                throw new IllegalArgumentException(
                        "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
            }
    
            // 模版抽象方法:将给定的参数类型和值名称解析为参数值。  由子类去实现
            // @PathVariable     --> 通过对uri解析后得到的decodedUriVariables值(常用)
            // @RequestParam     --> 通过 HttpServletRequest.getParameterValues(name) 获取(常用)
            // @RequestAttribute --> 通过 HttpServletRequest.getAttribute(name) 获取   <-- 这里的 scope 是 request
            // @SessionAttribute --> 略
            // @RequestHeader    --> 通过 HttpServletRequest.getHeaderValues(name) 获取
            // @CookieValue      --> 通过 HttpServletRequest.getCookies() 获取
            Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
            // 若解析出来值仍旧为null,那就走defaultValue (若指定了的话)
            if (arg == null) {
                // defaultValue也是支持占位符和SpEL
                if (namedValueInfo.defaultValue != null) {
                    arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
                }
                // 没有从请求解析出参数值, 也没有配置默认值, 还不是 Optional 类型的, 则通过 handleMissingValue 来进行处理, 一般是报异常
                // protected 的, 默认实现是抛出异常, 子类也可以进行实现
                // 它是个protected方法,默认抛出ServletRequestBindingException异常
                // 各子类都复写了此方法,转而抛出自己的异常(但都是ServletRequestBindingException的异常子类)
                else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                    handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
                }
                // handleNullValue是private方法, 来处理null值
                // 针对Bool类型有这个判断:Boolean.TYPE.equals(paramType) 就return Boolean.FALSE;
                // 此处注意:Boolean.TYPE = Class.getPrimitiveClass("boolean") 它指的基本类型的boolean
                // 如果到了这一步(value是null), 但你还是基本类型, 那就抛出异常了(只有boolean类型不会抛异常)
                // 这里多嘴一句, 即使请求传值为&bool=1, 效果同bool=true的(1:true 0:false) 并且不区分大小写(TrUe效果同true)
                // 【注】: 什么时候运行到这?当设置此参数是非必要的, 但是没有设置默认值, 还是基本类型参数, 请求也没有传此参数. 因为基本类型值它不知道给你啥默认值
                arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
            }
            // 解析出的值为空串 and 设置了默认值, 就使用默认值, 默认值也支持 ${} 和 #{}
            else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
                arg = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
            }
    
            // 数据绑定, 就是将解析出来的参数值转换为方法参数类型
            if (binderFactory != null) {
                WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
                try {
                    // 绑定器中的 Converter 转换
                    arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
                }
                catch (ConversionNotSupportedException ex) {
                    // 注意这个异常:MethodArgumentConversionNotSupportedException  类型不匹配的异常
                    throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                            namedValueInfo.name, parameter, ex.getCause());
                }
                catch (TypeMismatchException ex) {
                    //MethodArgumentTypeMismatchException是TypeMismatchException 的子类
                    throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                            namedValueInfo.name, parameter, ex.getCause());
                }
                // 转换后值为null && 默认值未设置 && 此参数必须 && 参数类型不是 Optional
                // 这里的校验是进来的值不为null但是经转换后变为null了
                // emm, 如果默认值设置了呢
                // protected, 默认调用 handleMissingValue
                if (arg == null && namedValueInfo.defaultValue == null &&
                        namedValueInfo.required && !nestedParameter.isOptional()) {
                    handleMissingValueAfterConversion(namedValueInfo.name, nestedParameter, webRequest);
                }
            }
    
            // protected的方法, 本类为空实现, 交给子类去复写(并不是必须的)
            // 唯独只有PathVariableMethodArgumentResolver把解析处理啊的值存储一下数据到 
            // HttpServletRequest.setAttribute中(若key已经存在也不会存储了)
            handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
    
            return arg;
        }
    
        /**
         * 方法参数信息封装: 参数名称、是否必须、默认值, 将解析后数据进行缓存
         */
        private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
            // 缓存
            NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
            if (namedValueInfo == null) {
                // createNamedValueInfo是抽象方法,子类必须实现
                namedValueInfo = createNamedValueInfo(parameter);
                // updateNamedValueInfo:这一步就是我们之前说过的为何Spring MVC可以根据参数名封装的方法
                // 如果info.name.isEmpty()的话(注解里没指定名称),就通过`parameter.getParameterName()`去获取参数名
                // 它还会处理注解指定的defaultValue:`
    	.....`等等都会被当作null处理
                // 都处理好后:new NamedValueInfo(name, info.required, defaultValue);(相当于吧注解解析成了此对象)
                namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
                this.namedValueInfoCache.put(parameter, namedValueInfo);
            }
            return namedValueInfo;
        }
    
        /**
         * 获取参数信息, 实现通常通过 MethodParameter#getParameterAnnotation(Class) 来获取注解信息
         */
        protected abstract NamedValueInfo createNamedValueInfo(MethodParameter parameter);
    
        private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValueInfo info) {
            String name = info.name;
            // 参数名称未配置
            if (info.name.isEmpty()) {
                // 这个就是在 class 文件中保留的参数名称信息
                name = parameter.getParameterName();
                if (name == null) {
                    throw new IllegalArgumentException(
                            "Name for argument of type [" + parameter.getNestedParameterType().getName() +
                            "] not specified, and parameter name information not found in class file either.");
                }
            }
            // emm, 这里使用的是 equals, 而 DEFAULT_NONE 是一串不可见字符组成的字符串, 注意这里不是 contains
            String defaultValue = (ValueConstants.DEFAULT_NONE.equals(info.defaultValue) ? null : info.defaultValue);
            return new NamedValueInfo(name, info.required, defaultValue);
        }
    
        // 解析 #{} 和 ${}
        @Nullable
        private Object resolveEmbeddedValuesAndExpressions(String value) {
            if (this.configurableBeanFactory == null || this.expressionContext == null) {
                return value;
            }
            String placeholdersResolved = this.configurableBeanFactory.resolveEmbeddedValue(value);
            BeanExpressionResolver exprResolver = this.configurableBeanFactory.getBeanExpressionResolver();
            if (exprResolver == null) {
                return value;
            }
            return exprResolver.evaluate(placeholdersResolved, this.expressionContext);
        }
    
        @Nullable
        protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request)
                throws Exception;
    
        protected void handleMissingValue(String name, MethodParameter parameter, NativeWebRequest request)
                throws Exception {
    
            handleMissingValue(name, parameter);
        }
    
        protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
            throw new ServletRequestBindingException("Missing argument '" + name +
                    "' for method parameter of type " + parameter.getNestedParameterType().getSimpleName());
        }
    
        protected void handleMissingValueAfterConversion(String name, MethodParameter parameter, NativeWebRequest request)
                throws Exception {
    
            handleMissingValue(name, parameter, request);
        }
    
        /**
         * 解析请求参数得到null值时的处理, 基本类型 boolean 处理, 其他基本类型抛出异常
         */
        @Nullable
        private Object handleNullValue(String name, @Nullable Object value, Class<?> paramType) {
            if (value == null) {
                // Boolean.TYPE 是基本类型 class
                if (Boolean.TYPE.equals(paramType)) {
                    return Boolean.FALSE;
                }
                // 其他基本类型抛出异常
                else if (paramType.isPrimitive()) {
                    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;
        }
    
    
        protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
                @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
        }
    
    
        /**
         * 方法参数信息封装: 参数名称、是否必须、默认值. 所有注解基本都可以配置这三个属性
         */
        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;
            }
        }
    
    }
    1. 基于MethodParameter构建NameValueInfo <-- 主要有name, defaultValue, required(其实主要是解析方法参数上标注的注解)
    2. 通过BeanExpressionResolver(${}占位符以及SpEL) 解析name
    3. 通过模版方法resolveNameHttpServletRequest, Http Headers, URI template variables 等等中获取对应的属性值(具体由子类去实现)
    4. arg==null这种情况的处理, 要么使用默认值, 若 required = true && arg == null, 则一般报出异常(boolean类型除外)
    5. 通过WebDataBinderarg转换成Methodparameter.getParameterType()类型(注意:这里仅仅只是用了数据转换而已,并没有用bind()方法)

    从上源码可以看出,抽象类已经定死了处理模版(方法为final的),留给子类需要做的事就不多了,大体还有如下三件事:

    1. 根据MethodParameter创建NameValueInfo(子类的实现可继承自NameValueInfo,就是对应注解的属性们)
    2. 根据方法参数名称nameHttpServletRequest, Http Headers, URI template variables等等中获取属性值
    3. arg == null这种情况的处理(非必须)

    PathVariableMethodArgumentResolver

    它帮助Spring MVC实现restful风格的URL。它用于处理标注有@PathVariable注解的方法参数,用于从URL中获取值(并不是?后面的参数)。 并且它还可以解析@PathVariable注解的value值不为空的Map(使用较少,个人不太建议使用)

    // 仅支持在 @RequestMapping 注解的 HandlerMethod 上使用
    public @interface PathVariable {
        // 参数名称
        @AliasFor("name")
        String value() default "";
    
        // 参数名称
        @AliasFor("value")
        String name() default "";
    
        // 默认是必须的
        boolean required() default true;
        
        // 【注意没有默认值的配置】
    }
    public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
            implements UriComponentsContributor {
    
        private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
    
        
        // @PathVariable 注解是必须的
        // Map 也可以
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            if (!parameter.hasParameterAnnotation(PathVariable.class)) {
                return false;
            }
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
                return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
            }
            return true;
        }
    
        // 根据 @PathVariable 注解解析出参数信息
        @Override
        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
            PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
            Assert.state(ann != null, "No PathVariable annotation");
            return new PathVariableNamedValueInfo(ann);
        }
    
        // 根据name去拿值的过程非常之简单,但是它和前面的只知识是有关联的
        // 至于这个attr是什么时候放进去的,AbstractHandlerMethodMapping.handleMatch()匹配处理器方法上
        // 通过UrlPathHelper.decodePathVariables() 把参数提取出来了,然后放进request属性上暂存了
        // 关于HandlerMapping内容, 可来这里:https://blog.csdn.net/f641385712/article/details/89810020
        @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);
        }
    
        @Override
        protected void handleMissingValue(String name, MethodParameter parameter) throws ServletRequestBindingException {
            // MissingPathVariableException是ServletRequestBindingException的子类
            throw new MissingPathVariableException(name, parameter);
        }
    
        @Override
        protected void handleMissingValueAfterConversion(
                String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
            // 同样的异常, ConversionService 转换后为 null 抛出的是同样的异常
            throw new MissingPathVariableException(name, parameter, true);
        }
    
        // 值完全处理结束后,把处理好的值放进请求域,方便view里渲染时候使用~
        // 抽象父类的handleResolvedValue方法,只有它复写了~
        @Override
        @SuppressWarnings("unchecked")
        protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
                @Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
    
            String key = View.PATH_VARIABLES;
            int scope = RequestAttributes.SCOPE_REQUEST;
            Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
            if (pathVars == null) {
                pathVars = new HashMap<>();
                request.setAttribute(key, pathVars, scope);
            }
            pathVars.put(name, arg);
        }
    
        @Override
        public void contributeMethodArgument(MethodParameter parameter, Object value,
                UriComponentsBuilder builder, Map<String, Object> uriVariables, ConversionService conversionService) {
    
            if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
                return;
            }
    
            PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
            String name = (ann != null && StringUtils.hasLength(ann.value()) ? ann.value() : parameter.getParameterName());
            String formatted = formatUriValue(conversionService, new TypeDescriptor(parameter.nestedIfOptional()), value);
            uriVariables.put(name, formatted);
        }
    
        @Nullable
        protected String formatUriValue(@Nullable ConversionService cs, @Nullable TypeDescriptor sourceType, Object value) {
            if (value instanceof String) {
                return (String) value;
            }
            else if (cs != null) {
                return (String) cs.convert(value, sourceType, STRING_TYPE_DESCRIPTOR);
            }
            else {
                return value.toString();
            }
        }
    
    
        private static class PathVariableNamedValueInfo extends NamedValueInfo {
    
            public PathVariableNamedValueInfo(PathVariable annotation) {
                // 原来这个常量在这里用到, 基本表示的是不支持默认值
                super(annotation.name(), annotation.required(), ValueConstants.DEFAULT_NONE);
            }
        }
    
    }
    关于@PathVariable的required=false使用注意事项
    @ResponseBody
    @GetMapping("/test/{id}")
    public Person test(@PathVariable(required = false) Integer id) { ... }

    以为这样写通过/test这个url就能访问到了,其实这样是不行的,会404。

    @ResponseBody
    @GetMapping({"/test/{id}", "/test"})
    public Person test(@PathVariable(required = false) Integer id) { ... }

    这样/test/test/1这两个url就都能正常work了

    RequestParamMethodArgumentResolver

    顾名思义,是解析标注有@RequestParam的方法入参解析器,这个注解比上面的注解强大很多了,它用于从请求参数(?后面的)中获取值完成封装。这是我们的绝大多数使用场景。除此之外,它还支持MultipartFile,也就是说能够从MultipartHttpServletRequest | HttpServletRequest 获取数据,并且并且并且还兜底处理没有标注任何注解的“简单类型”

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestParam {
    
        // 参数名称
        @AliasFor("name")
        String value() default "";
        // 参数名称
        @AliasFor("value")
        String name() default "";
    
        // 默认必须
        boolean required() default true;
    
        // 默认值
        String defaultValue() default ValueConstants.DEFAULT_NONE;
    
    }
    public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
            implements UriComponentsContributor {
    
        private static final TypeDescriptor STRING_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(String.class);
    
        // 这个参数老重要了:
        // true:表示参数类型是基本类型 参考BeanUtils#isSimpleProperty(什么Enum、Number、Date、URL、包装类型、以上类型的数组类型等等)
        // 如果是基本类型,即使你不写@RequestParam注解,它也是会走进来处理的(这个@PathVariable可不会)
        // fasle:除上以外的。  要想它处理就必须标注注解才行哦,比如List等
        // 默认值是false
        private final boolean useDefaultResolution;
    
    
        // 此构造只有`MvcUriComponentsBuilder`调用了  传入的false
        public RequestParamMethodArgumentResolver(boolean useDefaultResolution) {
            this.useDefaultResolution = useDefaultResolution;
        }
    
        // 传入了ConfigurableBeanFactory ,所以它支持处理占位符${...} 并且支持SpEL了
        // 此构造都在RequestMappingHandlerAdapter里调用,最后都会传入true来Catch-all Case  这种设计挺有意思的
        public RequestParamMethodArgumentResolver(@Nullable ConfigurableBeanFactory beanFactory,
                boolean useDefaultResolution) {
    
            super(beanFactory);
            this.useDefaultResolution = useDefaultResolution;
        }
    
    
    
        // 此处理器能处理如下Case:
        // 1、所有标注有@RequestParam注解的类型(非Map)/ 注解指定了value值的Map类型(自己提供转换器)
        // ======下面都表示没有标注@RequestParam注解了的=======
        // 1、不能标注有@RequestPart注解,否则直接不处理了
        // 2、是上传的request:isMultipartArgument() = true(MultipartFile类型或者对应的集合/数组类型  或者javax.servlet.http.Part对应结合/数组类型)
        // 3、useDefaultResolution=true情况下,"基本类型"也会处理
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            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;
                }
            }
            else {
                if (parameter.hasParameterAnnotation(RequestPart.class)) {
                    return false;
                }
                parameter = parameter.nestedIfOptional();
                if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
                    return true;
                }
                else if (this.useDefaultResolution) {
                    return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
                }
                else {
                    return false;
                }
            }
        }
    
        // 从这也可以看出:即使木有@RequestParam注解,也是可以创建出一个NamedValueInfo来的
        @Override
        protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
            RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
            return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
        }
    
        // 核心方法:根据Name 获取值(普通/文件上传)
        // 并且还有集合、数组等情况
        @Override
        @Nullable
        protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
            HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
    
            // 这块解析出来的是个MultipartFile或者其集合/数组
            if (servletRequest != null) {
                Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
                if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
                    return mpArg;
                }
            }
    
            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);
                }
            }
            
            // 若解析出来值仍旧为null,那处理完文件上传里木有,那就去参数里取吧
            // 由此可见:文件上传的优先级是高于请求参数的
            if (arg == null) {
                //小知识点:getParameter()其实本质是getParameterNames()[0]的效果
                // 强调一遍:?ids=1,2,3 结果是["1,2,3"](兼容方式,不建议使用。注意:只能是逗号分隔)
                // ?ids=1&ids=2&ids=3  结果是[1,2,3](标准的传值方式,建议使用)
                // 但是Spring MVC这两种都能用List接收  请务必注意他们的区别~~~
                String[] paramValues = request.getParameterValues(name);
                if (paramValues != null) {
                    arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
                }
            }
            return arg;
        }
    
    
        private static class RequestParamNamedValueInfo extends NamedValueInfo {
    
            public RequestParamNamedValueInfo() {
                super("", false, ValueConstants.DEFAULT_NONE);
            }
    
            public RequestParamNamedValueInfo(RequestParam annotation) {
                super(annotation.name(), annotation.required(), annotation.defaultValue());
            }
        }
    }

    可以看到这个ArgumentResolver处理器还是很强大的:不仅能处理标注了@RequestParam的参数,还能接收文件上传参数。甚至那些你平时使用中不标注该注解的封装也是它来兜底完成的。至于它如何兜底的,可以参见下面这个骚操作:

    public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
            implements BeanFactoryAware, InitializingBean {
        private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
            List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
    
            // Annotation-based argument resolution
            // 注意这里传入 false
            resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
            // Type-based argument resolution
            // Custom arguments
            // Catch-all, 兜底, 上面的都无法处理则由它们处理
            resolvers.add(new PrincipalMethodArgumentResolver());
            // 注意这里传入 true
            resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
            resolvers.add(new ServletModelAttributeMethodProcessor(true));
    
            return resolvers;
        }
    }

    其他

    在本文末,原作者搜集了一些自己使用过程中的一些疑惑进行解惑,希望也一样能帮助你豁然开朗。

    get请求如何传值数组、集合(List)

    如题的这个case太常见了有木有,我们经常会遇到使用get请求向后端需要传值的需求(比如根据ids批量查询)。但到底如何传,URL怎么写,应该是有傻傻分不清楚的不确定的情况。

    @PathVariable传参

        @ResponseBody
        @GetMapping("/test/{objects}")
        public Object test(@PathVariable List<Object> objects) {
            System.out.println(objects);
            return objects;
        }

    请求URL:/test/fsx,fsx,fsx。控制台打印:

    [fsx, fsx, fsx]

    集合接收成功(使用@PathVariable Object[] objects也是可以正常接收的)。 使用时应注意如下两点:

    1. 多个值只能使用,号分隔才行(否则会被当作一个值,放进数组/集合里,不会报错)
    2. @PathVariable注解是必须的。否则会交给ServletModelAttributeMethodProcessor兜底去处理,它要求有空构造所以反射创建实例会报错(数组/List)。(注意:如果是这样写ArrayList<Object> objects,那是不会报错的,只是值肯定是封装不进来的,一个空对象而已)

    说明:为何逗号分隔的String类型默认就能转化为数组,集合。请参考StringToCollectionConverter/StringToArrayConverter这种内置的GenericConverter通用转换器

    @RequestParam传参
        @ResponseBody
        @GetMapping("/test")
        public Object test(@RequestParam List<Object> objects) {
            System.out.println(objects);
            return objects;
        }

    请求URL:/test/?objects=1,2,3。控制台打印:

    [1, 2, 3]

    请求URL改为:/test/?objects=1&objects=2&objects=3。控制台打印:

    [1, 2, 3]

    两个请求的URL不一样,但都能正确的达到效果。(@RequestParam Object[] objects这么写两种URL也能正常封装)

    对此有如下这个细节你必须得注意:对于集合List而言@RequestParam注解是必须存在的,否则报错如下(因为交给兜底处理了):

    但如果你这么写String[] objects即使不写注解,也能够正常完成正确封装

    说明:Object[] objects这么写的话不写注解是不行的(报错如上)。至于原因,各位小伙伴可以自行思考,待续



    PS:需要注意的是,Spring MVC的这么多HandlerMethodArgumentResolver它的解析是有顺序的:如果多个HandlerMethodArgumentResolver都可以解析某一种类型,以顺序在前面的先解析(后面的就不会再执行解析了)。

    源码参考处:HandlerMethodArgumentResolverComposite.getArgumentResolver(MethodParameter parameter);

    由于RequestParamMethodArgumentResolver同样可以对Multipart文件上传进行解析,并且默认顺序在RequestPartMethodArgumentResolver之前,所以如果不添加@RequestPart注解,Multipart类型的参数会被RequestParamMethodArgumentResolver解析

  • 相关阅读:
    Map集合的四种遍历
    java 请求 google translate
    Linux 内核初级管理
    Linux GRUB
    Linux 系统启动流程
    Linux 任务计划 crontab
    Linux 进程管理工具
    Linux sudo实作
    Linux 进程管理
    Linux 网络配置命令:ip、ss
  • 原文地址:https://www.cnblogs.com/chenxingyang/p/15554903.html
Copyright © 2011-2022 走看看