SpringMvc流程
requestmappinghandlermapping是springmvc中一个比较核心的类,查看下它的类结构图:
initializingbean是个很神奇的接口,在spring每个容器的bean构造方法、属性设置之后,会先调用initializingbean接口的afterpropertiesset方法;
requestmappinghandlermapping的afterpropertiesset方法: 初始化了config对象,以及调用父类abstracthandlermethodmapping的afterpropertiesset,父类方法afterpropertiesset 逻辑是 inithandlermethods,这也是springmvc初始化寻找controller以及映射加载的核心逻辑;
@override public void afterpropertiesset() { this.config = new requestmappinginfo.builderconfiguration(); this.config.setpathhelper(geturlpathhelper()); this.config.setpathmatcher(getpathmatcher()); this.config.setsuffixpatternmatch(this.usesuffixpatternmatch); this.config.settrailingslashmatch(this.usetrailingslashmatch); this.config.setregisteredsuffixpatternmatch(this.useregisteredsuffixpatternmatch); this.config.setcontentnegotiationmanager(getcontentnegotiationmanager()); // 初始化config对象,主要属性就是pathmatcher; 以及调用父类 afterpropertiesset 方法,这是springmvc映射关系加载的核心; super.afterpropertiesset(); }
abstracthandlermethodmapping 的 inithandlermethods代码:
protected void inithandlermethods() { if (logger.isdebugenabled()) { logger.debug("looking for request mappings in application context: " + getapplicationcontext()); } string[] beannames = (this.detecthandlermethodsinancestorcontexts ? beanfactoryutils.beannamesfortypeincludingancestors(getapplicationcontext(), object.class) : getapplicationcontext().getbeannamesfortype(object.class));
// detecthandlermethodsinancestorcontexts 默认为false,代表不会检测springmvc父容器中的bean的映射关系 for (string beanname : beannames) {
//遍历容器中的beanname, 代理的对象跳过,获取当前bean的类型,调用ishandler判断是否是处理器(handler\controller) if (!beanname.startswith(scoped_target_name_prefix)) { class<?> beantype = null; try { beantype = getapplicationcontext().gettype(beanname); } catch (throwable ex) { // an unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isdebugenabled()) { logger.debug("could not resolve target class for bean with name '" + beanname + "'", ex); } } if (beantype != null && ishandler(beantype)) { //ishandler方法判断是否是controller,判断逻辑下面有; detecthandlermethods(beanname); //加载controller和请求映射关系 } } } handlermethodsinitialized(gethandlermethods()); // 该方法是个空实现 }
ishandler方法: 判断当前bean的class属性,标注了controller或者requestmapping注解,就会去加载controller和请求映射关系,如果不是handler,迭代下一个bean对象;
protected boolean ishandler(class<?> beantype) { return (annotatedelementutils.hasannotation(beantype, controller.class) || annotatedelementutils.hasannotation(beantype, requestmapping.class)); }
detecthandlermethods方法:
protected void detecthandlermethods(final object handler) { class<?> handlertype = (handler instanceof string ? getapplicationcontext().gettype((string) handler) : handler.getclass());//之前传入handler为string类型,此处去容器获取handler的class final class<?> usertype = classutils.getuserclass(handlertype); //处理class为cglib生成class,如果是cglib的获取父类class map<method, t> methods = methodintrospector.selectmethods(usertype, new methodintrospector.metadatalookup<t>() { @override public t inspect(method method) { return getmappingformethod(method, usertype); } }); if (logger.isdebugenabled()) { logger.debug(methods.size() + " request handler methods found on " + usertype + ": " + methods); } for (map.entry<method, t> entry : methods.entryset()) { method invocablemethod = aoputils.selectinvocablemethod(entry.getkey(), usertype); t mapping = entry.getvalue(); registerhandlermethod(handler, invocablemethod, mapping); } }
methodintrospector.selectmethods(..)方法是个很全面的解析方法:注释写得很详细,☆方法处,metadatalookup.inspect方法,往上看,调用的就是getmappingformethod方法获取requestmappinginfo对象;
public static <t> map<method, t> selectmethods(class<?> targettype, final metadatalookup<t> metadatalookup) { final map<method, t> methodmap = new linkedhashmap<method, t>(); set<class<?>> handlertypes = new linkedhashset<class<?>>(); class<?> specifichandlertype = null; if (!proxy.isproxyclass(targettype)) {
//handler class不是jdk代理生成的,加入到handlertypes集合,specifichandlertype为当前handler class handlertypes.add(targettype); specifichandlertype = targettype; } handlertypes.addall(arrays.aslist(targettype.getinterfaces())); /
/handler class实现的接口加入到handlertypes for (class<?> currenthandlertype : handlertypes) { final class<?> targetclass = (specifichandlertype != null ? specifichandlertype : currenthandlertype); reflectionutils.dowithmethods(currenthandlertype, new reflectionutils.methodcallback() {
//该工具类方法,遍历了该currenthandlertype本类中所有的方法 // 调用的是 getdeclaredmethods(),然后遍历method数组,调用dowith回调处理method方法 public void dowith(method method) { method specificmethod = classutils.getmostspecificmethod(method, targetclass); t result = metadatalookup.inspect(specificmethod);
// ☆ 核心!!! 这里处理了方法以及类上的映射关系,并且返回泛型t,实际类型是requesmappinginfo if (result != null) { method bridgedmethod = bridgemethodresolver.findbridgedmethod(specificmethod); if (bridgedmethod == specificmethod || metadatalookup.inspect(bridgedmethod) == null) { methodmap.put(specificmethod, result); } } } }, reflectionutils.user_declared_methods);
// reflectionutils.user_declared_methods是个methodfilter,作用是过滤方法是用户定义、且非桥接类型的方法; } return methodmap; }
requestmappinghandlermapping 的 getmappingformethod 方法:先分析方法上的映射关系,再分析类所在方法上的映射关系,然后结合处理;
下面一点点记录我查看这个方法的发现;
protected requestmappinginfo getmappingformethod(method method, class<?> handlertype) { requestmappinginfo info = createrequestmappinginfo(method);
// 解析类上requestmapping注解 if (info != null) { requestmappinginfo typeinfo = createrequestmappinginfo(handlertype);// 解析方法上@requestmapping注解 if (typeinfo != null) { info = typeinfo.combine(info); //方法上requestmapping注解不为空,就需要结合分析 } } return info; }
createrequestmappinginfo 方法:
private requestmappinginfo createrequestmappinginfo(annotatedelement element) {
//调用spring注解工具类annotatedelementutils获取方法上注解 requestmapping requestmapping = annotatedelementutils.findmergedannotation(element, requestmapping.class); requestcondition<?> condition = (element instanceof class<?> ? getcustomtypecondition((class<?>) element) : getcustommethodcondition((method) element));
//requestmappinghandlermapping两个方法都是返回null,空实现 return (requestmapping != null ? createrequestmappinginfo(requestmapping, condition) : null); }
具体的requestmappinginfo的构造采用建造者模式还是其他模式的?
protected requestmappinginfo createrequestmappinginfo( requestmapping requestmapping, requestcondition<?> customcondition) { //customcondition一般都为null return requestmappinginfo .paths(resolveembeddedvaluesinpatterns(requestmapping.path()))// @requestmapping(path={....}) 将path属性设置上去 .methods(requestmapping.method()) // @requestmapping(method={....}) 将method属性设置上去 .params(requestmapping.params()) // @requestmapping(method={....}) 将method属性设置上去 .headers(requestmapping.headers()) // @requestmapping(headers={....}) 将headers属性设置上去 .consumes(requestmapping.consumes()) // @requestmapping(consumes={....}) 将consumes属性设置上去 .produces(requestmapping.produces()) // @requestmapping(produces={....}) 将produces属性设置上去 .mappingname(requestmapping.name()) // @requestmapping(name={....}) 将name属性设置上去 .customcondition(customcondition) .options(this.config) .build(); }
这里只分析一个开头、一个结尾这样;
requestmappinginfo 的 paths 方法:
public static builder paths(string... paths) { // paths是@requestmapping的path属性,字符串数组,这里用可变参数来接收,效果一样
return new defaultbuilder(paths); }
builder接口所有方法都返回builder对象,defaultbuilder持有一堆属性,可以看到都是@reuqestmapping的属性;
paths方法就将注解的path属性注入到defaultbuilder中,其他方法methods、params、headers、consumes、produces、mappingname、customcondition都是这个套路;
而 options注入的config属性 ,最开始 afterpropertiesset 里 ,this.config = new requestmappinginfo.builderconfiguration();
就是将requestmappinghandlemapping中的config作为defaultbuilder的options注入; 最后就是build方法。
defaultbuilder 的 build方法:
public requestmappinginfo build() { contentnegotiationmanager manager = this.options.getcontentnegotiationmanager(); // patternsrequestcondition构造的主要属性就是paths,代表了映射的路径,不以/开头会添加 / 这个开头 patternsrequestcondition patternscondition = new patternsrequestcondition( this.paths, this.options.geturlpathhelper(), this.options.getpathmatcher(), this.options.usesuffixpatternmatch(), this.options.usetrailingslashmatch(), this.options.getfileextensions()); return new requestmappinginfo(this.mappingname, patternscondition, new requestmethodsrequestcondition(methods), new paramsrequestcondition(this.params), new headersrequestcondition(this.headers), new consumesrequestcondition(this.consumes, this.headers), new producesrequestcondition(this.produces, this.headers, manager), this.customcondition); // customcondition通常为null }
build方法返回 requestmappinginfo,其中构造入参都是xxxrequestcondition这种,他们都实现了requestcondition接口;
private patternsrequestcondition(collection<string> patterns, urlpathhelper urlpathhelper, pathmatcher pathmatcher, boolean usesuffixpatternmatch, boolean usetrailingslashmatch, list<string> fileextensions) { //这里就是 prependleadingslash 会判断 @requestmapping注解的 path属性,不是以 /开头会添加 / this.patterns = collections.unmodifiableset(prependleadingslash(patterns));
this.pathhelper = (urlpathhelper != null ? urlpathhelper : new urlpathhelper()); this.pathmatcher = (pathmatcher != null ? pathmatcher : new antpathmatcher()); this.usesuffixpatternmatch = usesuffixpatternmatch; this.usetrailingslashmatch = usetrailingslashmatch; if (fileextensions != null) { for (string fileextension : fileextensions) { if (fileextension.charat(0) != '.') { fileextension = "." + fileextension; } this.fileextensions.add(fileextension); } } }
private static set<string> prependleadingslash(collection<string> patterns) { if (patterns == null) { return collections.emptyset(); } set<string> result = new linkedhashset<string>(patterns.size()); for (string pattern : patterns) { if (stringutils.haslength(pattern) && !pattern.startswith("/")) { //url不以 /开头就会自动添加 / pattern = "/" + pattern; } result.add(pattern); } return result; }
回到requestmappinginfo的构造方法,将@requestmapping的所有属性都以 requestcondition的实现类 形式保存到 requestmappinginfo对象中;
接口requestcondition定义了三个方法,1.combine:一般用来 方法级别@requestmapping与类级别@requestmapping结合,返回新的(通常是requestmappinginfo);
2.getmatchingcondition:检查request对象是否满足条件,返回一个新的满足条件的requestmappinginfo实例(t泛型用都是requestmappinginfo);
3.compareto 用来多个匹配的情况排序挑选最合适的
public interface requestcondition<t> { t combine(t other); t getmatchingcondition(httpservletrequest request); int compareto(t other, httpservletrequest request); }
至此 回到 requestmappinghandlermapping 的 getmappingformethod方法 ,第一个方法级别的createrequestmappinginfo方法分析完毕,下面两行解析了标注在 类上的 注解,并且返回 requestmappinginfo对象,
第188行就是类上标注了@requestmapping注解,和方法上同样标注@requestmapping结合处理的步骤:调用类上的requestmappinginfo的combine方法
查看requestmappinginfo对象的combine方法:
public requestmappinginfo combine(requestmappinginfo other) {
// requestmapping的name属性的处理方法,一般name属性很少写,处理方式:两个都不为空就返回this.name#other.name;有一个为空 就返回另外一个name string name = combinenames(other);
//下面逻辑a分析 调用antpathmatcher的combine方法,将类上url和方法上url组合并放入新patternsrequestcondition patternsrequestcondition patterns = this.patternscondition.combine(other.patternscondition);
//下面逻辑b分析,并且接下来的methods、params、headers等等实现方式大体一致 requestmethodsrequestcondition methods = this.methodscondition.combine(other.methodscondition); paramsrequestcondition params = this.paramscondition.combine(other.paramscondition); headersrequestcondition headers = this.headerscondition.combine(other.headerscondition);
//!!comsume和produce判断逻辑不是相加,方法上的该属性优先级高于类级别上的 consumesrequestcondition consumes = this.consumescondition.combine(other.consumescondition); producesrequestcondition produces = this.producescondition.combine(other.producescondition); requestconditionholder custom = this.customconditionholder.combine(other.customconditionholder); return new requestmappinginfo(name, patterns,//返回一个新的requestmappinginfo对象,其中所有requestcondition都是新创建的对象 methods, params, headers, consumes, produces, custom.getcondition()); }
逻辑a: patternsrequestcondition 之前介绍过,其属性patterns 就是@requestmapping的path / value 属性的集合,且判断 path是否以 / 开头,如果不是会自动补全 / 开头;
其实现了requestcondition接口,查看其combine方法
public patternsrequestcondition combine(patternsrequestcondition other) {
// result作为新的请求路径集合 set<string> result = new linkedhashset<string>();
//类上注解@requestmapping path不为空,方法上注解注解@requestmapping path不为空
//此处的antpathmatcher就是requestmappinghandlermapping对象里的antpathmatcher对象
//@requestmapping path属性是集合类型的,这类似笛卡尔积形式 调用antpathmatcher的combine方式,进行url组合 加入到result
if (!this.patterns.isempty() && !other.patterns.isempty()) { for (string pattern1 : this.patterns) {
for (string pattern2 : other.patterns) { result.add(this.pathmatcher.combine(pattern1, pattern2)); } } }
//已经说明有一方为空了,只要判断另外一方是否为空,不为空直接加入set<string> else if (!this.patterns.isempty()) { result.addall(this.patterns); } else if (!other.patterns.isempty()) { result.addall(other.patterns); } else { result.add(""); } /返回了一个新的patternsrequestcondition对象,patterns属性就是当前方法的请求路径 return new patternsrequestcondition(result, this.pathhelper, this.pathmatcher, this.usesuffixpatternmatch, this.usetrailingslashmatch, this.fileextensions); / }
逻辑a-1:antpathmatcher对象如何对请求路径进行结合combine?
类上path | 方法上path | 结合后path |
null | null | |
/hotels | null | /hotels |
null | /hotels | /hotels |
/hotels | /bookings | /hotels/bookings |
/hotels | bookings | /hotels/bookings |
/hotels/* | /bookings | /hotels/bookings |
/hotels/** | /bookings | /hotels/**/bookings |
/hotels | {hotel} | /hotels/{hotel} |
/hotels/* | {hotel} | /hotels/{hotel} |
/hotels/** | {hotel} | /hotels/**/{hotel} |
/*.html | hotels.html | /hotels.html |
/*.html | /hotels | /hotels.html |
/*.html | /*.txt | illegalargumentexception |
逻辑b:requestmethodsrequestcondition 的 combine 方法,方法上注解@requestmapping的method加入到类上注解的method属性里,然后返回一个全新的requestmethodsrequestcondition,持有新的method集合;
public requestmethodsrequestcondition combine(requestmethodsrequestcondition other) { set<requestmethod> set = new linkedhashset<requestmethod>(this.methods); set.addall(other.methods); return new requestmethodsrequestcondition(set); }
getmappingformethod方法调用结束,返回结合后的requestmappinginfo对象; 回到methodintrospector.selectmethods方法,第19行就是调用的getmappingformethod方法,返回requestmappinginfo对象result,result不为空之后,
会筛选不是桥接方法,存入methodmap这个map对象,key-type是method,value-type是requestmappinginfo类型;
该方法selectmethods将controller / handler中所有方法都进行判断加载请求映射,返回methodmap对象;
1 public static <t> map<method, t> selectmethods(class<?> targettype, final metadatalookup<t> metadatalookup) { 2 final map<method, t> methodmap = new linkedhashmap<method, t>(); 3 set<class<?>> handlertypes = new linkedhashset<class<?>>(); 4 class<?> specifichandlertype = null; 5 6 if (!proxy.isproxyclass(targettype)) { 7 handlertypes.add(targettype); 8 specifichandlertype = targettype; 9 } 10 handlertypes.addall(arrays.aslist(targettype.getinterfaces())); 11 12 for (class<?> currenthandlertype : handlertypes) { 13 final class<?> targetclass = (specifichandlertype != null ? specifichandlertype : currenthandlertype); 14 15 reflectionutils.dowithmethods(currenthandlertype, new reflectionutils.methodcallback() { 16 @override 17 public void dowith(method method) { 18 method specificmethod = classutils.getmostspecificmethod(method, targetclass); 19 t result = metadatalookup.inspect(specificmethod); 20 if (result != null) { 21 method bridgedmethod = bridgemethodresolver.findbridgedmethod(specificmethod); 22 if (bridgedmethod == specificmethod || metadatalookup.inspect(bridgedmethod) == null) { 23 methodmap.put(specificmethod, result); 24 } 25 } 26 } 27 }, reflectionutils.user_declared_methods); 28 } 29 30 return methodmap; 31 }
回到最开始的分析detecthandlermethods方法:methods对象就是上面返回的methodmap,如果日志设置了debug,每遍历一个controller都会输出日志;
1 protected void detecthandlermethods(final object handler) { 2 class<?> handlertype = (handler instanceof string ? 3 getapplicationcontext().gettype((string) handler) : handler.getclass()); 4 final class<?> usertype = classutils.getuserclass(handlertype); 5 6 map<method, t> methods = methodintrospector.selectmethods(usertype, 7 new methodintrospector.metadatalookup<t>() { 8 @override 9 public t inspect(method method) { 10 return getmappingformethod(method, usertype); 11 } 12 }); 13 14 if (logger.isdebugenabled()) { 15 logger.debug(methods.size() + " request handler methods found on " + usertype + ": " + methods); 16 } 17 for (map.entry<method, t> entry : methods.entryset()) {//遍历methods,并且调用registerhandlermethod注册映射信息 18 method invocablemethod = aoputils.selectinvocablemethod(entry.getkey(), usertype); 19 t mapping = entry.getvalue(); 20 registerhandlermethod(handler, invocablemethod, mapping); 21 } 22 }
registerhandlermethod:总结 :
requestmappinghandlermapping.mappingregistry属性
|
key-type | value-type |
mappinglookup |
requestmappinginfo | handlermethod对象 |
urllookup |
请求路径url | requestmappinginfo |
namelookup | controller name中大写字母#方法名(如uc#test) | handlermethod对象 |
registry |
requestmappinginfo |
mappingregistration对象(持有 |
protected void registerhandlermethod(object handler, method method, t mapping) { this.mappingregistry.register(mapping, handler, method); } // this对象指requestmappinghandlermapping,mapping是requestmappinginfo对象,handler是controler的name,method是当前@requestmapping方法 public void register(t mapping, object handler, method method) { this.readwritelock.writelock().lock(); //可重入锁 写锁上锁 这里不太明白为什么要上锁 try {
//创建新的handlermethod对象 下面逻辑c 介绍handlermethod 逻辑d 分析createhandlermethod方法 handlermethod handlermethod = createhandlermethod(handler, method);
//校验唯一性,一个requestmappinginfo对应一个handlermethod,如果根据requestmappinginfo找到不同的hm 抛出异常 assertuniquemethodmapping(handlermethod, mapping); //info级别日志 比如mapped "{[/user/test]}" onto public java.lang.string demo2.usercontroller.test(javax.servlet.http.httpservletrequest,javax.servlet.http.httpservletresponse) if (logger.isinfoenabled()) { logger.info("mapped \"" + mapping + "\" onto " + handlermethod); }
//this指requestmappinghandlermapping.mappingregistry,mappinglookup保存着requestmappinginfo--handlermethod对象 this.mappinglookup.put(mapping, handlermethod); //获取 mapping 的patternsrequestcondition的patterns,也就是拼接的url路径,并且路径不包含* ?的就加入到集合返回 , list<string> directurls = getdirecturls(mapping);
for (string url : directurls) {
//mappingregistry的urllookup保存着 url--requestmappinginfo对象 this.urllookup.add(url, mapping); } string name = null; if (getnamingstrategy() != null) {
//name属性感觉没用,如果@requestmapping有name属性就是这个属性 如果没有就是 controller名字中的大写字母#方法名字,比如uc#test name = getnamingstrategy().getname(handlermethod, mapping);
//mappingregistry的namelookup保存着 name--handlermethod集合 addmappingname(name, handlermethod); } corsconfiguration corsconfig = initcorsconfiguration(handler, method, mapping); if (corsconfig != null) { this.corslookup.put(handlermethod, corsconfig); }
//mappingregistry的registry保存着requestmappinginfo--mappingregistration,mappingregistration几乎有映射的所有信息 this.registry.put(mapping, new mappingregistration<t>(mapping, handlermethod, directurls, name)); } finally { this.readwritelock.writelock().unlock(); //可重入锁 写锁 释放锁 } }
逻辑c:handlermethod对象 属性有bean,就是controller对象实例;beanfactory当前spring容器;beantype就是controller的类型;method就是handler method;birdgemethod是handler method的桥接方法;methodparameter是handler method的方法参数,handlermethod一般为null;
handlermethod,作用spring给出了:一个handler method对象,包含了method以及controller对象,此外提供了便捷方式获取方法入参、返回值、注解等等;
逻辑d:createhandlermethod方法只是调用了handlermethod的构造方法,构造方法中对方法入参进行了处理;
1 protected handlermethod createhandlermethod(object handler, method method) { 2 handlermethod handlermethod; 3 if (handler instanceof string) { 4 string beanname = (string) handler; 5 handlermethod = new handlermethod(beanname, 6 getapplicationcontext().getautowirecapablebeanfactory(), method); 7 } 8 else { 9 handlermethod = new handlermethod(handler, method); 10 } 11 return handlermethod; 12 } 13 14 public handlermethod(string beanname, beanfactory beanfactory, method method) { 15 assert.hastext(beanname, "bean name is required"); 16 assert.notnull(beanfactory, "beanfactory is required"); 17 assert.notnull(method, "method is required"); 18 this.bean = beanname; //controller beanname 19 this.beanfactory = beanfactory; //当前controller所在spring工厂 20 this.beantype = classutils.getuserclass(beanfactory.gettype(beanname)); //获取当前controller类型 21 this.method = method; //当前handler method 22 this.bridgedmethod = bridgemethodresolver.findbridgedmethod(method); //查找method的桥接方法,没有桥接方法就是返回自身 23 this.parameters = initmethodparameters(); //初始化methodparameter对象 设置了每个methodparameter的method、parameterindex属性 具体方法下图 24 this.resolvedfromhandlermethod = null; 25 }
至此,registerhandlermethod方法分析完毕,detecthandlermethods方法分析完成,
spring主要做了哪些工作:将所有请求映射关系保存到上面requestmappinghandlermapping的mappingregistry的相关属性中,详情见上面表格。
分析过springmvc的请求流程 springmvc流程
篇幅太长,只分析如何找根据请求找到对应的handler? 遍历handlermapping对象,调用其gethanlder方法查找controller / handler , requestmappinghandlermapping对象的父类abstracthandlermapping实现了gethandler方法,方法最开始object handler = gethandlerinternal(request); 那么我们从abstracthandlermapping 的 gethandlerinternal开始记录.
1 protected handlermethod gethandlerinternal(httpservletrequest request) throws exception {
// 根据request请求路径以及servlet-mapping得到要请求url 2 string lookuppath = geturlpathhelper().getlookuppathforrequest(request); 3 if (logger.isdebugenabled()) { 4 logger.debug("looking up handler method for path " + lookuppath); 5 } 6 this.mappingregistry.acquirereadlock(); //读锁 上锁 7 try {
// 这里就是mvc寻找controller匹配的方法! 下面花大篇幅介绍下 8 handlermethod handlermethod = lookuphandlermethod(lookuppath, request);
9 if (logger.isdebugenabled()) { 10 if (handlermethod != null) { 11 logger.debug("returning handler method [" + handlermethod + "]"); 12 } 13 else { 14 logger.debug("did not find handler method for [" + lookuppath + "]"); 15 } 16 }
//找到handlermethod,但bean是controller beanname,用beanfactory getbean替换bean 17 return (handlermethod != null ? handlermethod.createwithresolvedbean() : null); 18 } 19 finally { 20 this.mappingregistry.releasereadlock(); 21 } 22 }
lookuphandlermethod方法:
逻辑是这样的,先根据请求的url 从 requestmappinghandlermapping的mappingregistry的urllookup中尝试寻找requestmappinginfo;
寻找大致分为两种情况:一种请求url清楚,不需要通配符比对,那肯定可以直接找到requestmappinginfo集合,创建match对象并且添加到集合里面,然后根据规则对match集合排序选出最优解;
第二种情况url带有通配符,那需要遍历映射关系再重复第一种情况。
1 protected handlermethod lookuphandlermethod(string lookuppath, httpservletrequest request) throws exception { 2 list<match> matches = new arraylist<match>();
//return this.urllookup.get(urlpath);调用mappingregistry的urllookup根据url寻找requestmappinginfo 3 list<t> directpathmatches = this.mappingregistry.getmappingsbyurl(lookuppath);
4 if (directpathmatches != null) {
//遍历找到的requestmappinginfo集合, 然后寻找匹配的对象并处理添加到matches集合,见 逻辑e 分析 5 addmatchingmappings(directpathmatches, matches, request);
6 } 7 if (matches.isempty()) { //matches为空,有可能是因为通配符匹配的情况需要再次匹配 8 // no choice but to go through all mappings... 9 addmatchingmappings(this.mappingregistry.getmappings().keyset(), matches, request); 10 } 11 12 if (!matches.isempty()) {
//返回一个matchcomparator对象
// 持有comparator属性,并且compare方法是调用了requestmappinginfo的compareto 13 comparator<match> comparator = new matchcomparator(getmappingcomparator(request));
//说到底排序还是调用了requestmappinginfo的compareto方法, 也存在优先级之分 url路径>params>headers>comsume>produce>method 排序分析见文章最后 14 collections.sort(matches, comparator); 15 if (logger.istraceenabled()) { 16 logger.trace("found " + matches.size() + " matching mapping(s) for [" + 17 lookuppath + "] : " + matches); 18 } 19 match bestmatch = matches.get(0); //找到最优匹配 20 if (matches.size() > 1) { 21 if (corsutils.ispreflightrequest(request)) { 22 return preflight_ambiguous_match; 23 } 24 match secondbestmatch = matches.get(1); 25 if (comparator.compare(bestmatch, secondbestmatch) == 0) { //存在两个匹配且相等 抛出异常 26 method m1 = bestmatch.handlermethod.getmethod(); 27 method m2 = secondbestmatch.handlermethod.getmethod(); 28 throw new illegalstateexception("ambiguous handler methods mapped for http path '" + 29 request.getrequesturl() + "': {" + m1 + ", " + m2 + "}"); 30 } 31 } 32 handlematch(bestmatch.mapping, lookuppath, request); //解析url变量,完成设置request属性等工作 33 return bestmatch.handlermethod; //返回最优匹配的handlermethod对象 34 } 35 else { //没找到handlermethod 就返回null 36 return handlenomatch(this.mappingregistry.getmappings().keyset(), lookuppath, request); 37 } 38 }
逻辑e:遍历找到的requestmappinginfo集合,调用requestmappinginfo的getmatchcondition进行匹配以获取匹配的requestmappinginfo对象;寻找到合适的requestmappinginfo对象之后,创建一个match对象加入matches集合;
mappingregistry 的 getmappings方法返回mappinglookup属性,上述表格mappinglookup 存放 requestmappinginfo--handlermethod,根据requestmappinginfo对象从map中取对象,(逻辑g分析 requestmappinginfo重写了的hashcode以及equals方法)。 match对象持有requestmappinginfo以及handlermethod属性;此处方法调用结束matches可能包含多个match结果;
requestmappinginfo的getmatchingcondition方法
1 public requestmappinginfo getmatchingcondition(httpservletrequest request) {
//如果requestmappinginfo没有指定methods属性,返回requestmappinginfo本身,否则方法匹配 2 requestmethodsrequestcondition methods = this.methodscondition.getmatchingcondition(request);
//下面几个匹配逻辑是一样的,匹配了返回自身,没匹配返回null,具体参数作用、如何匹配看吧; 3 paramsrequestcondition params = this.paramscondition.getmatchingcondition(request); 4 headersrequestcondition headers = this.headerscondition.getmatchingcondition(request); 5 consumesrequestcondition consumes = this.consumescondition.getmatchingcondition(request); 6 producesrequestcondition produces = this.producescondition.getmatchingcondition(request); 7 //有一个条件匹配不上就直接返回null 8 if (methods == null || params == null || headers == null || consumes == null || produces == null) {
9 return null; 10 } 11 //其他匹配上了,最重要的匹配请求url, 路径匹配作为 逻辑f 分析 12 patternsrequestcondition patterns = this.patternscondition.getmatchingcondition(request);
13 if (patterns == null) { 14 return null; 15 } 16 17 requestconditionholder custom = this.customconditionholder.getmatchingcondition(request); 18 if (custom == null) { 19 return null; 20 } 21 22 return new requestmappinginfo(this.name, patterns,
//寻找到匹配之后,构造一个新的requestmappinginfo对象,持有上述匹配之后的结果返回 23 methods, params, headers, consumes, produces, custom.getcondition()); 24 }
逻辑f:patternsrequestcondition匹配
调用patternsrequestcondition 的 getmatchingpattern 方法进行url匹配;遍历patternsrequestcondition的 patterns属性,逐个getmatchingpattern进行比较,匹配上将pattern存入集合,并且使用antpatterncomparator进行排序,排序之后集合加入到一个新的patternsrequestcondition对象中;
//pattern就是patterns属性当前迭代的元素,lookuppath就是servlet-mapping下请求url
1 private string getmatchingpattern(string pattern, string lookuppath) { 2 if (pattern.equals(lookuppath)) { //两者相等 无疑义直接返回 这种是没有通配符 * ?这种都会很容易匹配到并且返回 3 return pattern; 4 } 5 if (this.usesuffixpatternmatch) { // usesuffixpatternmatch默认为true 6 if (!this.fileextensions.isempty() && lookuppath.indexof('.') != -1) { // fileextensions默认为空 7 for (string extension : this.fileextensions) { 8 if (this.pathmatcher.match(pattern + extension, lookuppath)) { 9 return pattern + extension; 10 } 11 } 12 } 13 else { 14 boolean hassuffix = pattern.indexof('.') != -1; //pattern字符串是否有 . 15 if (!hassuffix && this.pathmatcher.match(pattern + ".*", lookuppath)) { //没有 . 就用antpathmatcher的match匹配 pattern.* lookuppath 16 return pattern + ".*"; 17 } 18 } 19 } 20 if (this.pathmatcher.match(pattern, lookuppath)) {
// 用antpathmatcher的match匹配 pattern lookuppath,匹配上就返回pattern 21 return pattern; 22 } 23 if (this.usetrailingslashmatch) { 24 if (!pattern.endswith("/") && this.pathmatcher.match(pattern + "/", lookuppath)) { 25 return pattern +"/"; 26 } 27 } 28 return null; 29 }
逻辑g:先介绍下为什么要看requestmappinginfo的hashcode以及equals方法?requestmappinginfo作为key存储在map中,肯定需要重写hashcode以及equals方法;
requestmappinginfo的hashcode以及equals方法: 比较的时候会先调用hashcode判断值是否相等,相等再比较equals方法,如果相等则认为是同一个对象;
先来看hashcode方法,将requestmappinginfo的所有requestcondition属性按公式求和,这些属性都是abstractrequestcondition,equals和hashcode方法都调用了getcontent方法,而abstractrequestcondition的各种实现类的getcontent方法,比如patternsrequestcondition实现方式就是返回patterns(url)集合;比如requestmethodsrequestcondition实现就是返回methods集合;
requestmappinginfo
1 public boolean equals(object other) { 2 if (this == other) { 3 return true; 4 } 5 if (!(other instanceof requestmappinginfo)) { 6 return false; 7 } 8 requestmappinginfo otherinfo = (requestmappinginfo) other; 9 return (this.patternscondition.equals(otherinfo.patternscondition) && 10 this.methodscondition.equals(otherinfo.methodscondition) && 11 this.paramscondition.equals(otherinfo.paramscondition) && 12 this.headerscondition.equals(otherinfo.headerscondition) && 13 this.consumescondition.equals(otherinfo.consumescondition) && 14 this.producescondition.equals(otherinfo.producescondition) && 15 this.customconditionholder.equals(otherinfo.customconditionholder)); 16 } 17 18 @override 19 public int hashcode() { 20 return (this.patternscondition.hashcode() * 31 + // primary differentiation 21 this.methodscondition.hashcode() + this.paramscondition.hashcode() + 22 this.headerscondition.hashcode() + this.consumescondition.hashcode() + 23 this.producescondition.hashcode() + this.customconditionholder.hashcode()); 24 }
abstractrequestcondition
1 public boolean equals(object obj) { 2 if (this == obj) { 3 return true; 4 } 5 if (obj != null && getclass() == obj.getclass()) { 6 abstractrequestcondition<?> other = (abstractrequestcondition<?>) obj; 7 return getcontent().equals(other.getcontent()); 8 } 9 return false; 10 } 11 12 @override 13 public int hashcode() { 14 return getcontent().hashcode(); 15 }
分析到上面,gethandlerinternal已经找到了对应的handlermethod对象,调用gethandlerexecutionchain封装成handlerexecutionchain;
1 protected handlerexecutionchain gethandlerexecutionchain(object handler, httpservletrequest request) { 2 handlerexecutionchain chain = (handler instanceof handlerexecutionchain ? 3 (handlerexecutionchain) handler : new handlerexecutionchain(handler));
// //构造一个handlerexecutionchain对象,持有handlermethod 4 5 string lookuppath = this.urlpathhelper.getlookuppathforrequest(request); 6 for (handlerinterceptor interceptor : this.adaptedinterceptors) {
//adaptedinterceptors在开启<mvc:annotation-drvien/>之后不为空,多了一个mappedinterceptor拦截器 7 if (interceptor instanceof mappedinterceptor) { 8 mappedinterceptor mappedinterceptor = (mappedinterceptor) interceptor; 9 if (mappedinterceptor.matches(lookuppath, this.pathmatcher)) { 10 chain.addinterceptor(mappedinterceptor.getinterceptor());
//将conversionserviceexposinginterceptor添加到handlerexecutionchain的interceptorlist属性中 11 } 12 } 13 else { 14 chain.addinterceptor(interceptor); 15 } 16 } 17 return chain; //返回handlerexecutionchain对象 18 }
tip1:这个requestmappinghandlermapping的mappedinterceptor是从哪里注入的呢?
开启了<mvc:annotation-driven />之后 spring向容器中注入了这样两个bean的定义,mappedinterceptor,该对象持有conversionserviceexposinginterceptor对象;
容器中有了mappedinterceptor对象,什么时候给requestmappinghandlermapping设置的adaptedinterceptors呢?通过打断点分析到,requestmappinghandlermapping实现了applicationcontextaware接口,spring向其注入applicationcontext的时候,调用了initapplicationcontext方法,不断进入方法最后进入到父类abstracthandlermapping的initapplicationcontext方法,
1 protected void initapplicationcontext() throws beansexception { 2 extendinterceptors(this.interceptors); 3 detectmappedinterceptors(this.adaptedinterceptors); //此处添加了requestmappinghandlermapping的adaptedinterceptors 4 initinterceptors(); 5 } 6 7 protected void detectmappedinterceptors(list<handlerinterceptor> mappedinterceptors) {
//这里将容器中的mappedinterceptor添加到了requestmappinghandlermapping的adaptedinterceptors 8 mappedinterceptors.addall(
9 beanfactoryutils.beansoftypeincludingancestors( 10 getapplicationcontext(), mappedinterceptor.class, true, false).values()); 11 }
至此,如何找到handlermethod已经分析完毕;
总结
springmvc请求寻找规则 : 如果一个请求同时匹配上多个方法,按照如下顺序选择执行哪个方法:
先url匹配的方法 >>>>> params满足的方法 >>>>> headers 满足的方法 >>>>>>consume满足的方法 >>>> produce 满足的方法 >>>> method满足的方法
如果一个请求匹配上了多个requestmappinginfo筛选:
之前介绍过排序是调用 requestmappinginfo的compareto进行排序
1 public int compareto(requestmappinginfo other, httpservletrequest request) { 2 int result = this.patternscondition.compareto(other.getpatternscondition(), request); //优先url进行匹配 3 if (result != 0) { 4 return result; 5 } 6 result = this.paramscondition.compareto(other.getparamscondition(), request); 7 if (result != 0) { 8 return result; 9 } 10 result = this.headerscondition.compareto(other.getheaderscondition(), request); 11 if (result != 0) { 12 return result; 13 } 14 result = this.consumescondition.compareto(other.getconsumescondition(), request); 15 if (result != 0) { 16 return result; 17 } 18 result = this.producescondition.compareto(other.getproducescondition(), request); 19 if (result != 0) { 20 return result; 21 } 22 result = this.methodscondition.compareto(other.getmethodscondition(), request); 23 if (result != 0) { 24 return result; 25 } 26 result = this.customconditionholder.compareto(other.customconditionholder, request); 27 if (result != 0) { 28 return result; 29 } 30 return 0; 31 }
介绍下url如何排序吧,其他类似; 假设两个url /get1 可以被匹配 /get* 以及 /get?
1 public int compareto(patternsrequestcondition other, httpservletrequest request) { 2 string lookuppath = this.pathhelper.getlookuppathforrequest(request); 3 comparator<string> patterncomparator = this.pathmatcher.getpatterncomparator(lookuppath); //获取antpatterncomparator比较器 4 iterator<string> iterator = this.patterns.iterator(); 5 iterator<string> iteratorother = other.patterns.iterator(); 6 while (iterator.hasnext() && iteratorother.hasnext()) { 7 int result = patterncomparator.compare(iterator.next(), iteratorother.next()); //url比较规则在这里 8 if (result != 0) { 9 return result; 10 } 11 } 12 if (iterator.hasnext()) { 13 return -1; 14 } 15 else if (iteratorother.hasnext()) { 16 return 1; 17 } 18 else { 19 return 0; 20 } 21 }
url比较规则:按照请求url通配符按一定权重计算排序顺序,{个数+*个数+ ** 个数 ;所以 get* 比get?排在前面;
1 public int compare(string pattern1, string pattern2) { //例子中pattern1为 /get* pattern2为/get? 2 patterninfo info1 = new patterninfo(pattern1); //具体查看下面构造方法 3 patterninfo info2 = new patterninfo(pattern2); 4 5 if (info1.isleastspecific() && info2.isleastspecific()) { 6 return 0; 7 } 8 else if (info1.isleastspecific()) { 9 return 1; 10 } 11 else if (info2.isleastspecific()) { //上面三种情况是 比较 /**的情况 12 return -1; 13 } 14 15 boolean pattern1equalspath = pattern1.equals(path); 16 boolean pattern2equalspath = pattern2.equals(path); 17 if (pattern1equalspath && pattern2equalspath) { 18 return 0; 19 } 20 else if (pattern1equalspath) { 21 return -1; 22 } 23 else if (pattern2equalspath) { //这三种情况是比较 pattern1 pattern2存在和请求url完全匹配的情况 24 return 1; 25 } 26 27 if (info1.isprefixpattern() && info2.getdoublewildcards() == 0) { 28 return 1; 29 } 30 else if (info2.isprefixpattern() && info1.getdoublewildcards() == 0) { //哪个pattern的 /**多 哪个排在前面 31 return -1; 32 } 33 34 if (info1.gettotalcount() != info2.gettotalcount()) { 35 return info1.gettotalcount() - info2.gettotalcount(); //按照权重来排序了 {算1 *算1 **算2 哪个大哪个排前面 /get*权重为1排前面 36 } 37 38 if (info1.getlength() != info2.getlength()) { 39 return info2.getlength() - info1.getlength(); 40 } 41 42 if (info1.getsinglewildcards() < info2.getsinglewildcards()) { 43 return -1; 44 } 45 else if (info2.getsinglewildcards() < info1.getsinglewildcards()) { 46 return 1; 47 } 48 49 if (info1.geturivars() < info2.geturivars()) { 50 return -1; 51 } 52 else if (info2.geturivars() < info1.geturivars()) { 53 return 1; 54 } 55 56 return 0; 57 } 58 59 public patterninfo(string pattern) { 60 this.pattern = pattern; 61 if (this.pattern != null) { 62 initcounters(); 63 this.catchallpattern = this.pattern.equals("/**"); //代表匹配所有就是pattern为 /** 64 this.prefixpattern = !this.catchallpattern && this.pattern.endswith("/**"); 65 } 66 if (this.urivars == 0) { 67 this.length = (this.pattern != null ? this.pattern.length() : 0); 68 } 69 } 70 71 protected void initcounters() { 72 int pos = 0; 73 while (pos < this.pattern.length()) { 74 if (this.pattern.charat(pos) == '{') { //存在变量 则urivars自增 75 this.urivars++; 76 pos++; 77 } 78 else if (this.pattern.charat(pos) == '*') { //解析到* 79 if (pos + 1 < this.pattern.length() && this.pattern.charat(pos + 1) == '*') { 80 this.doublewildcards++; // doublewildcards代表有两个*的 81 pos += 2; 82 } 83 else if (pos > 0 && !this.pattern.substring(pos - 1).equals(".*")) { //最后一位是* 且倒数第二位不是 * 84 this.singlewildcards++; // singlewildcards代表有单个* 85 pos++; 86 } 87 else { 88 pos++; 89 } 90 } 91 else { 92 pos++; 93 } 94 } 95 }
下一篇: 清明节气养生运动篇 推荐最适合养生的运动