SpringMvc的handlerMapping的源码解读

HandlerMapping的类继承图如下:


其中有一个DefaultAnnotationHanlerMapping过时了,就没有画出来,顶级接口HandlerMapping,只定义了一个方法getHandler,而这个方法被AbstractHandlerMapping实现了,而在AbstractHandlerMapping的getHandler方法里定义了一个getHandlerInternal这个方法,这个方法交由继承AbstractHandlerMapping的子孙类去实现,也就是用了常见的设计模式,模板方法模式,父类定义整个结构或者模板,由子类去实现,很多第三方工具类中这种设计模式很常见.

一.初始化

HandlerMapping的组件是什么时候初始化的,这个就要说到SpringMvc的核心类DispatcherServlet,DispatcherServlet实现了Servelt标准,会被当做一个一个执行链的一个对象添加到servlet的链中,这个是用了责任链模式,就像糖葫芦串一样,肯定是吃完第一个吃第二个,当项目初始化的时候,依次调用init方法,总会有调用DispatcherServletinit方法的一天(大致是这样,实际是由父类FramWorkServlet根据不同的情况调用了onfresh方法,onfresh调用initStrategies方法,加载9个组件),其中initHandlerMappings就是handlerMapping组件,这几个组件加载都有一个共性,就是先是从容器中加载对应的组件对象,如果没有这个对象,则抛出NoSuchBeanException异常,在这个异常里面去加载spring的默认配置,(MultipartResolver除外,如果没有配置是没有默认组件的),这些默认的配置就在spring-webmvc\org\springframework\web\servlet包下,有一个DispatcherServlet.properties配置文件,这些配置文件中配置了默认的一些组件,有的组件会有多个配置,在加载的时候只会加载第一个.

接着说HandlerMapping的加载,hanlerMapping也是采用此种手法,但是如果没有配置,会从容器中获取,通常会是多个,这些对象是保存在DispatcherServlet的handlerMappings这个集合当中.将来有请求过来,会遍历这个list,从中找到能够处理这个url的handler,那这就要说到这些组件是初始化url对handler的映射的.

刚刚说到实际上在dispatcherServelt容器在初始化的时候,handler组件的对象其实已经就在spring容器了,initHandlerMappings的作用只不过是将他们放到集合当中,那这些handler何时加载自己的配置的?这里举该组件三个类来说

1.SimpleUrlHandlerMapping

SimpleUrlHanlerMapping里面的初始化是重写了其父类AbstractHandlerMapping的initApplicationContext方法,这个方法是由AbstractHandlerMapping继承WebApplicationObjectSupport这个类实现来的,当spring容器启动时会初始化这些方法,那我们看看AbstractHandlerMapping的initApplicationContext方法干了啥,贴代码:


这三个方法都和initInterceptor有关,第一个方法extendInterceptors是个模板方法,目前在子类中并没有实现,而第二个字面意思是查找映射过的拦截器主要作用是用来查找已经配置自定义的拦截器和spring自己的拦截器,似乎关系不大,而下面这个方法是初始化拦截器,似乎也和handler没关系,那我们现在知道了,父类AbstractHandlerMapping作为顶级定义,他的初始化方法只加载了拦截器,其实换个角度想想也是,拦截器是通用的,而不同的HandlerMapping实现的策略肯定不一样,不然抽象出AbstractHandlerMapping就没有意义,那我们接着看看SimpleUrlHanlerMapping重写了initApplicationContext方法是做了什么事情.


首先,他上来二话不说先调用了父类的方法,加载了拦截器的信息,接下来这个registerHandlers似乎是重点,register是注册的意思,他似乎要开始注册handler了,看下代码


他内部维护了一个map,当注册handler的时候他就去遍历这个map,map的key是url,object就是Handler,那这个map是怎么来的,这就要说到SimpleUrlHandlerMapping的用法,如果配了用SimpleUrlHandlerMapping,那么control需要继承AbstractController方法,里面有个handleRequestInternal方法需要实现,再在配置文件中加上请求的路径和对应的类,这个就是map的由来,

一个control只有一个方法,但是一个方法可以有多个url,然后接着看registerHandler方法,这是父类的方法,也就是抽象出来公有的方法,我们看下


这个方法很重要,他是先根据handler的名字去容器中找到这个对象,然后在从handlerMap中去根据url取这个handler,如果说url里面有这个handler,就将根据名字取的和根据url取的进行对比,看是不是同一个,如果不是同一个则抛异常,如果说根据url取的handler是空的,说明还没有注册,他又判断了这个url是不是"/"这个请求,如果是则将这个传进来的handler设置为根handler,如果url等于"/*",他又将这个handler设置为默认的handler,如果都不是就将他放进这个urlMap当中,就是将url和handler进行注册,将来可以通过getHandler方法,取到handler.

2.BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping内部就一个方法,那他的初始化方法肯定不是自己实现了,而是继承了父类的方法,我们就看下AbstractDetectingUrlHandlerMapping类

结构和SimpleUrlHandlerMapping方法类似,也是先调用了父类的initApplicationContext()方法,加载拦截器,然后调用自己的detectHandlers方法

重点看下这个方法


他首先是获取了整个容器中所有的bean对象,然后去遍历这些bean,在遍历bean的时候,他有个方法determineUrlsForHandler,这个方法正是BeanNameUrlHandlerMapping唯一的方法


BeanNameUrlHandlerMapping是控制器的name作为url,所以在这个方法里,他先判断了这个bean的名字是不是已"/"开头,如果不是的话又去获取他的别名,遍历他的别名,判断是不是以"/"开头,最后将最终结果返回给detectHandlers方法,最后又调用了registerHandler,也就是上面我们分析的AbstractHandlerMapping的registerHandler的方法,我们看下BeanNameUrlHandlerMapping的用法:

他在control层和SimpleUrlHandlerMapping一样都需要继承AbstractController,不同的是配置文件


也可以在此配置他的别名,将来别名和本名就是两个url通过一个control处理

3.RequestMappingHandlerMapping

这个组件也就是我们常用的根据@Controller和@RequestMapping来映射,内部比上面两个较复杂,他是实现了InitializingBean接口,当这个bean被创建的时候,会调用InitializingBean的afterPropertiesSet方法,这个方法也是加载RequestMappingHandlerMapping的入口


config对象主要是用于根据@Request对象里面不同的属性,找到相对应的handler,暂且可以放后面说,先说下super.afterPropertiesSet()方法,该方法又调用了initHandlerMethods


该方法的执行过程和BeanNameUrlHandlerMapping有异曲同工之妙,他首先也是获取了整个容器中的bean对象,然后去判断是不是handler,我们看下isHandler方法


也就说他判断从spring容器取出bean是不是handler条件就是这个对象有没有Control和RequstMapping这两个注解,如果他是handler,他又根据这个handler去查找他的方法,之前的hanlder都是对象级别的,因为就一个方法,而RequestMappingHandlerMapping实现了handler可以是方法级别的,

g

这里主要做的就是根据这个对象找到能够作为handler的方法然后放进一个map当中,其中key就是根据@RequestMapping中的value封装成的RequestMappingInfo对象,他的key会被RequestMappingInfo存在patternsCondition 当中,可以看下结构:


紧接着遍历这些方法,重点是registerHandlerMethod这个方法


它让mappingRegisterry调用了他的register方法.先看下mappingRegisterry是个啥玩意


他的内部也是一个个键值的对象,然后我们接着看下register方法做了啥


读写锁这个东西先不说,首先他是把handler和method封装成了一个HandlerMethod对象,

然后直接将mapping和handlerMethod放入一个map当中,此时的mapping还是刚刚说的RequestMappingInfo对象,紧接着他就去遍历了mapping里面的patternsCondition的值,也就是@RequestMapping的url,然后在去遍历这个url值,将url和mapping添加到urlLookup当中,紧接着又做了一个命名策略的事,就是将类中的大写字母提取出来后面紧跟#号再加方法的名字,


addMappingName就是把这个name为键,这个类和方法名放入到nameLookUp当中去


在接着就是处理了@CrossOrigin注解,这个注解是springmvc-4.2新加的功能,.主要是解决跨域问题,和我们分析的关系不大最后就是将刚刚说的几个map封装成了一个类,以@RequestMapping的url为值放到了一个map当中,至此,整个加载过程分析结束

RequestMappingHandlerMapping功能很强大,在设计上也会相对来说比较繁琐,这里面省略了很多细节的东西,比如根据@Request里面的值,创建不同的对象以便将来用相应的策略来查找handler等等.

二.查找handler

上面说过了springmvc是如何加载handler以及spring容器是如何初始化handler的,现在说一说springmvc是如何查找handler的.

查找的入口在DispatcherServlet的doDispatch方法当中


我们看下getHandler方法


这里他实际上去遍历了之前加载的handler组件,然后查找那一个handler能狗组装出执行链,这个方法,在父类AbstractHandlerMapping中有定义


其中getHandlerInternal方法也是个模版方法,各个子类实现的方法不同,所以由他们提供而返回的handlerChain里面则封装了具体的handler和拦截器,将来可以在执行具体的方法之前执行拦截器的方法,就达到了拦截的效果,最核心的还是getHandlerInternal这个方法,获取handler我们还是以刚刚的三个类分析,但是可以分为两种,beanNameUrlHandlerMapping和SimpleUrlHandlerMapping都是继承AbstractUrlHandlerMapping,所以实现由他实现,我们看下他的方法

1.AbstractUrlHandlerMapping


他显示从request中取到访问请求的路径,在从之前添加的map当中去找这个handler,紧接着又根据url判断了一些情况,基本和添加的思路相反,不多说.

2.RequestMappingHandlerMapping

他的查找方法来自于AbstractHandlerMethodMapping方法

他的逻辑首先也是获取到url,然后在根据url去获取handler,其中主要是lookupHandlerMethod这个方法


他在这里主要做的事情就是根据之前添加进去的url和handler找到合适的handler,如url添加的是"/test/{id}",他会去找到合适的请求,这里就不细说了,有兴趣可以研究研究

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 引言 一直以来都在使用Spring mvc,能够熟练使用它的各种组件。但是,它一直像个黑盒一样,我并不知道它内部是...
    yoqu阅读 883评论 0 24
  • 回顾2014年发现,80岁的Alice Gerrard发新砖了。陌生的名字,她是谁?她的歌好听吗? AMG上Ger...
    loveisbug阅读 202评论 0 2
  • 并非因为岁月泥泞才无处落脚 暖阳仅仅是又一次冰霜的预演 在死囚沦为陪审之前 相聚早已沦为 未经判决的流亡 幽默足够...
    Stellavallis阅读 224评论 0 0