当前位置: 移动技术网 > IT编程>开发语言>Java > HandlerMethodArgumentResolver(一):Controller方法入参自动封装器【享学Spring MVC】

HandlerMethodArgumentResolver(一):Controller方法入参自动封装器【享学Spring MVC】

2019年08月28日  | 移动技术网IT编程  | 我要评论

虐杀原形死机,安吉小羊官网,北京纯爱故事

每篇一句

你的工作效率高,老板会认为你强度不够。你代码bug多,各种生产环境救火,老板会觉得你是团队的核心成员。

前言

在享受spring mvc带给你便捷的时候,你是否曾经这样疑问过:controllerhandler方法参数能够自动完成封装(有时即使没有@pathvariable@requestparam@requestbody等注解都可),甚至在方法参数任意位置写httpservletrequesthttpsessionwriter...等类型的参数,它自动就有值了便可直接使用。
对此你是否想问一句:spring mvc它是怎么办到的?那么本文就揭开它的神秘面纱,还你一片"清白"。

spring mvc作为一个最为流行的web框架,早早已经成为了实际意义上的标准化(框架),特别是随着struts2的突然崩塌,spring mvc几乎一骑绝尘,因此深入了解它有着深远的意义

spring mvc它只需要区区几个注解就能够让一个普通的java方法成为一个handler处理器,并且还能有自动参数封装、返回值视图处理/渲染等一系列强大功能,让coder的精力更加的聚焦在自己的业务。

像jsf、google web toolkit、grails framework等web框架至少我是没有用过的。
这里有个轻量级的web框架:play framework设计上我个人觉得还挺有意思,有兴趣的可以玩玩

handlermethodargumentresolver

策略接口:用于在给定请求的上下文中将方法参数解析为参数值。简单的理解为:它负责处理你handler方法里的所有入参:包括自动封装、自动赋值、校验等等。有了它才能会让spring mvc处理入参显得那么高级、那么自动化。
spring mvc内置了非常非常多的实现,当然若还不能满足你的需求,你依旧可以自定义和自己注册,后面我会给出自定义的示例。

有个形象的公式:handlermethodargumentresolver = handlermethod + argument(参数) + resolver(解析器)
解释为:它是handlermethod方法的解析器,将httpservletrequest(header + body 中的内容)解析为handlermethod方法的参数(method parameters)

// @since 3.1   handlermethod 方法中 参数解析器
public interface handlermethodargumentresolver {

    // 判断 handlermethodargumentresolver 是否支持 methodparameter
    // (ps: 一般都是通过 参数上面的注解|参数的类型)
    boolean supportsparameter(methodparameter parameter);
    
    // 从nativewebrequest中获取数据,modelandviewcontainer用来提供访问model
    // methodparameter parameter:请求参数
    // webdatabinderfactory用于创建一个webdatabinder用于数据绑定、校验
    @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来实现,它是最为重要的分支(分类)

// @since 3.1  负责从路径变量、请求、头等中拿到值。(都可以指定name、required、默认值等属性)
// 子类需要做如下事:获取方法参数的命名值信息、将名称解析为参数值
// 当需要参数值时处理缺少的参数值、可选地处理解析值

//特别注意的是:默认值可以使用${}占位符,或者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;
    }
    public abstractnamedvaluemethodargumentresolver(@nullable configurablebeanfactory beanfactory) {
        this.configurablebeanfactory = beanfactory;
        // 默认是requestscope
        this.expressioncontext = (beanfactory != null ? new beanexpressioncontext(beanfactory, new requestscope()) : null);
    }

    // protected的内部类  所以所有子类(注解)都是用友这三个属性值的
    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;
        }
    }

    // 核心方法  注意此方法是final的,并不希望子类覆盖掉他~
    @override
    @nullable
    public final object resolveargument(methodparameter parameter, @nullable modelandviewcontainer mavcontainer, nativewebrequest webrequest, @nullable webdatabinderfactory binderfactory) throws exception {

        // 创建 methodparameter 对应的 namedvalueinfo
        namedvalueinfo namedvalueinfo = getnamedvalueinfo(parameter);
        // 支持到了java 8 中支持的 java.util.optional
        methodparameter nestedparameter = parameter.nestedifoptional();

        // name属性(也就是注解标注的value/name属性)这里既会解析占位符,还会解析spel表达式,非常强大
        // 因为此时的 name 可能还是被 ${} 符号包裹, 则通过 beanexpressionresolver 来进行解析
        object resolvedname = resolvestringvalue(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 = resolvestringvalue(namedvalueinfo.defaultvalue);

            // 若 arg == null && defaultvalue == null && 非 optional 类型的参数 则通过 handlemissingvalue 来进行处理, 一般是报异常
            } else if (namedvalueinfo.required && !nestedparameter.isoptional()) {
                
                // 它是个protected方法,默认抛出servletrequestbindingexception异常
                // 各子类都复写了此方法,转而抛出自己的异常(但都是servletrequestbindingexception的异常子类)
                handlemissingvalue(namedvalueinfo.name, nestedparameter, webrequest);
            }
    
            // handlenullvalue是private方法,来处理null值
            // 针对bool类型有这个判断:boolean.type.equals(paramtype) 就return boolean.false;
            // 此处注意:boolean.type = class.getprimitiveclass("boolean") 它指的基本类型的boolean,而不是boolean类型哦~~~
            // 如果到了这一步(value是null),但你还是基本类型,那就抛出异常了(只有boolean类型不会抛异常哦~)
            // 这里多嘴一句,即使请求传值为&bool=1,效果同bool=true的(1:true 0:false) 并且不区分大小写哦(true效果同true)
            arg = handlenullvalue(namedvalueinfo.name, arg, nestedparameter.getnestedparametertype());
        }
        // 兼容空串,若传入的是空串,依旧还是使用默认值(默认值支持占位符和spel)
        else if ("".equals(arg) && namedvalueinfo.defaultvalue != null) {
            arg = resolvestringvalue(namedvalueinfo.defaultvalue);
        }

        // 完成自动化的数据绑定~~~
        if (binderfactory != null) {
            webdatabinder binder = binderfactory.createbinder(webrequest, null, namedvalueinfo.name);
            try {
                // 通过数据绑定器里的converter转换器把arg转换为指定类型的数值
                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());

            }
        }

        // protected的方法,本类为空实现,交给子类去复写(并不是必须的)
        // 唯独只有pathvariablemethodargumentresolver把解析处理啊的值存储一下数据到 
        // httpservletrequest.setattribute中(若key已经存在也不会存储了)
        handleresolvedvalue(arg, namedvalueinfo.name, parameter, mavcontainer, webrequest);
        return arg;
    }


    // 此处有缓存,记录下每一个methodparameter对象   value是namedvalueinfo值
    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:`\n\t\.....`等等都会被当作null处理
            // 都处理好后:new namedvalueinfo(name, info.required, defaultvalue);(相当于吧注解解析成了此对象嘛~~)
            namedvalueinfo = updatenamedvalueinfo(parameter, namedvalueinfo);
            this.namedvalueinfocache.put(parameter, namedvalueinfo);
        }
        return namedvalueinfo;
    }

    // 抽象方法 
    protected abstract namedvalueinfo createnamedvalueinfo(methodparameter parameter);
    // 由子类根据名称,去把值拿出来
    protected abstract object resolvename(string name, methodparameter parameter, nativewebrequest request) throws exception;
}

该抽象类中定义了解析参数的主逻辑(模版逻辑),子类只需要实现对应的抽象模版方法即可。
对此部分的处理步骤,我把它简述如下:

  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(使用较少,个人不太建议使用)~

---

uricomponentscontributor接口:通过查看方法参数和参数值并决定应更新目标url的哪个部分,为构建uricomponents的策略接口。

// @since 4.0 出现得还是比较晚的
public interface uricomponentscontributor {

    // 此方法完全同handlermethodargumentresolver的这个方法~~~
    boolean supportsparameter(methodparameter parameter);
    // 处理给定的方法参数,然后更新uricomponentsbuilder,或者使用uri变量添加到映射中,以便在处理完所有参数后用于扩展uri~~~
    void contributemethodargument(methodparameter parameter, object value, uricomponentsbuilder builder,
            map<string, object> urivariables, conversionservice conversionservice);
}

它的三个实现类:
在这里插入图片描述
关于此接口的使用,后面再重点介绍,此处建议自动选择性忽略。

---

// @since 3.0 需要注意的是:它只支持标注在@requestmapping的方法(处理器)上使用~
@target(elementtype.parameter)
@retention(retentionpolicy.runtime)
@documented
public @interface pathvariable {
    @aliasfor("name")
    string value() default "";
    @aliasfor("value")
    string name() default "";
    
    // 注意:它并没有defaultvalue哦~

    // @since 4.3.3  它也是标记为false非必须的~~~~
    boolean required() default true;
}

// @since 3.1
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;
    }

    @override
    protected namedvalueinfo createnamedvalueinfo(methodparameter parameter) {
        pathvariable ann = parameter.getparameterannotation(pathvariable.class);
        return new pathvariablenamedvalueinfo(ann);
    }
    private static class pathvariablenamedvalueinfo extends namedvalueinfo {
        public pathvariablenamedvalueinfo(pathvariable annotation) {
            // 默认值使用的default_none~~~
            super(annotation.name(), annotation.required(), valueconstants.default_none);
        }
    }

    // 根据name去拿值的过程非常之简单,但是它和前面的只知识是有关联的
    // 至于这个attr是什么时候放进去的,abstracthandlermethodmapping.handlematch()匹配处理器方法上
    // 通过urlpathhelper.decodepathvariables() 把参数提取出来了,然后放进request属性上暂存了~~~
    // 关于handlermapping内容,可来这里:https://blog.csdn.net/f641385712/article/details/89810020
    @override
    @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);
    }

    // missingpathvariableexception是servletrequestbindingexception的子类
    @override
    protected void handlemissingvalue(string name, methodparameter parameter) throws servletrequestbindingexception {
        throw new missingpathvariableexception(name, parameter);
    }


    // 值完全处理结束后,把处理好的值放进请求域,方便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);
    }
    ...
}

关于@pathvariable的使用,不用再给例子了。
唯一需要说一下如果类型是map类型的情况下的使用注意事项,如下:

@pathvariable("jsonstr") map<string,object> map

希望把jsonstr对应的字符串解析成键值对封装进map里。那么你必须,必须,必须注册了能处理此字符串的converter/propertyeditor(自定义)。使用起来相对麻烦,但技术隐蔽性高。我一般不建议这么来用~


关于@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了~

> @pathvariable的required=false使用较少,一般用于在用url传多个值时,但有些值是非必传的时候使用。比如这样的url:"/user/{id}/{name}","/user/{id}","/user"

requestparammethodargumentresolver

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

// @since 2.5
@target(elementtype.parameter)
@retention(retentionpolicy.runtime)
@documented
public @interface requestparam {
    @aliasfor("name")
    string value() default "";
     // @since 4.2
    @aliasfor("value")
    string name() default "";
    boolean required() default true;
    string defaultvalue() default valueconstants.default_none;
}
// @since 3.1
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;
    }
    ...
}

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

public class requestmappinghandleradapter extends abstracthandlermethodadapter implements beanfactoryaware, initializingbean {
    ...
    private list<handlermethodargumentresolver> getdefaultargumentresolvers() {
        list<handlermethodargumentresolver> resolvers = new arraylist<>();
        // annotation-based argument resolution
        resolvers.add(new requestparammethodargumentresolver(getbeanfactory(), false));
        ...
        // catch-all  兜底
        resolvers.add(new requestparammethodargumentresolver(getbeanfactory(), true));
        resolvers.add(new servletmodelattributemethodprocessor(true));

        return resolvers;
    }
    ...
}

可以看到servletmodelattributemethodprocessorrequestparammethodargumentresolver一样,也是有兜底的效果的。

---


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

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解析


总结

本文是你理解spring mvc强大的自动数据封装功能非常重要的一篇文章。它介绍了handlermethodargumentresolver的功能和基本使用,以及深入介绍了最为重要的两个注解@pathvariable@requestparam以及各自对应的argumentresolver处理器。
由于这个体系庞大,所以我会分多个章节进行描述,欢迎订阅和持续关注~

相关阅读

【小家spring】spring mvc容器的web九大组件之---handlermapping源码详解(二)---requestmappinghandlermapping系列

handlermethodargumentresolver(一):controller方法入参自动封装器(将参数parameter解析为值)【享学spring mvc】
handlermethodargumentresolver(二):map参数类型和固定参数类型【享学spring mvc】
handlermethodargumentresolver(三):基于httpmessageconverter消息转换器的参数处理器【享学spring mvc】

知识交流

==the last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~==

若对技术内容感兴趣可以加入wx群交流:java高工、架构师3群
若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群

==若对spring、springboot、mybatis等源码分析感兴趣,可加我wx:fsx641385712,手动邀请你入群一起飞==

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网