HandlerMethodArgumentResolver(一):Controller方法入参自动封装器【享学Spring MVC】
每篇一句
你的工作效率高,老板会认为你强度不够。你代码bug多,各种生产环境救火,老板会觉得你是团队的核心成员。
前言
在享受spring mvc
带给你便捷的时候,你是否曾经这样疑问过:controller
的handler
方法参数能够自动完成封装(有时即使没有@pathvariable
、@requestparam
、@requestbody
等注解都可),甚至在方法参数任意位置写httpservletrequest
、httpsession
、writer
...等类型的参数,它自动就有值了便可直接使用。
对此你是否想问一句: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; }
基于这个接口的处理器实现类不可谓不丰富,非常之多。我截图如下:
因为子类众多,所以我分类进行说明。我把它分为四类进行描述:
- 基于
name
- 数据类型是
map
的 - 固定参数类型
- 基于
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; }
该抽象类中定义了解析参数的主逻辑(模版逻辑),子类只需要实现对应的抽象模版方法即可。
对此部分的处理步骤,我把它简述如下:
- 基于
methodparameter
构建namevalueinfo
<-- 主要有name, defaultvalue, required
(其实主要是解析方法参数上标注的注解~) - 通过
beanexpressionresolver
(${}
占位符以及spel
) 解析name
- 通过模版方法
resolvename
从httpservletrequest, http headers, uri template variables
等等中获取对应的属性值(具体由子类去实现) - 对
arg==null
这种情况的处理, 要么使用默认值, 若required = true && arg == null
, 则一般报出异常(boolean类型除外~) - 通过
webdatabinder
将arg
转换成methodparameter.getparametertype()
类型(注意:这里仅仅只是用了数据转换而已,并没有用bind()
方法)
该抽象类继承树如下:
从上源码可以看出,抽象类已经定死了处理模版(方法为final的),留给子类需要做的事就不多了,大体还有如下三件事:
- 根据
methodparameter
创建namevalueinfo
(子类的实现可继承自namevalueinfo
,就是对应注解的属性们) - 根据方法参数名称
name
从httpservletrequest, http headers, uri template variables
等等中获取属性值 - 对
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; } ... }
可以看到servletmodelattributemethodprocessor
和requestparammethodargumentresolver
一样,也是有兜底的效果的。
---
在本文末,我搜集了一些自己使用过程中的一些疑惑进行解惑,希望也一样能帮助你豁然开朗。
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
也是可以正常接收的)。
使用时应注意如下两点:
- 多个值只能使用
,
号分隔才行(否则会被当作一个值,放进数组/集合里,不会报错) -
@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,手动邀请你入群一起飞==