SpringMvc的handlerMapping的源碼解讀

HandlerMapping的類繼承圖如下:


其中有一個(gè)DefaultAnnotationHanlerMapping過時(shí)了,就沒有畫出來(lái),頂級(jí)接口HandlerMapping,只定義了一個(gè)方法getHandler,而這個(gè)方法被AbstractHandlerMapping實(shí)現(xiàn)了,而在AbstractHandlerMapping的getHandler方法里定義了一個(gè)getHandlerInternal這個(gè)方法,這個(gè)方法交由繼承AbstractHandlerMapping的子孫類去實(shí)現(xiàn),也就是用了常見的設(shè)計(jì)模式,模板方法模式,父類定義整個(gè)結(jié)構(gòu)或者模板,由子類去實(shí)現(xiàn),很多第三方工具類中這種設(shè)計(jì)模式很常見.

一.初始化

HandlerMapping的組件是什么時(shí)候初始化的,這個(gè)就要說到SpringMvc的核心類DispatcherServlet,DispatcherServlet實(shí)現(xiàn)了Servelt標(biāo)準(zhǔn),會(huì)被當(dāng)做一個(gè)一個(gè)執(zhí)行鏈的一個(gè)對(duì)象添加到servlet的鏈中,這個(gè)是用了責(zé)任鏈模式,就像糖葫蘆串一樣,肯定是吃完第一個(gè)吃第二個(gè),當(dāng)項(xiàng)目初始化的時(shí)候,依次調(diào)用init方法,總會(huì)有調(diào)用DispatcherServletinit方法的一天(大致是這樣,實(shí)際是由父類FramWorkServlet根據(jù)不同的情況調(diào)用了onfresh方法,onfresh調(diào)用initStrategies方法,加載9個(gè)組件),其中initHandlerMappings就是handlerMapping組件,這幾個(gè)組件加載都有一個(gè)共性,就是先是從容器中加載對(duì)應(yīng)的組件對(duì)象,如果沒有這個(gè)對(duì)象,則拋出NoSuchBeanException異常,在這個(gè)異常里面去加載spring的默認(rèn)配置,(MultipartResolver除外,如果沒有配置是沒有默認(rèn)組件的),這些默認(rèn)的配置就在spring-webmvc\org\springframework\web\servlet包下,有一個(gè)DispatcherServlet.properties配置文件,這些配置文件中配置了默認(rèn)的一些組件,有的組件會(huì)有多個(gè)配置,在加載的時(shí)候只會(huì)加載第一個(gè).

接著說HandlerMapping的加載,hanlerMapping也是采用此種手法,但是如果沒有配置,會(huì)從容器中獲取,通常會(huì)是多個(gè),這些對(duì)象是保存在DispatcherServlet的handlerMappings這個(gè)集合當(dāng)中.將來(lái)有請(qǐng)求過來(lái),會(huì)遍歷這個(gè)list,從中找到能夠處理這個(gè)url的handler,那這就要說到這些組件是初始化url對(duì)handler的映射的.

剛剛說到實(shí)際上在dispatcherServelt容器在初始化的時(shí)候,handler組件的對(duì)象其實(shí)已經(jīng)就在spring容器了,initHandlerMappings的作用只不過是將他們放到集合當(dāng)中,那這些handler何時(shí)加載自己的配置的?這里舉該組件三個(gè)類來(lái)說

1.SimpleUrlHandlerMapping

SimpleUrlHanlerMapping里面的初始化是重寫了其父類AbstractHandlerMapping的initApplicationContext方法,這個(gè)方法是由AbstractHandlerMapping繼承WebApplicationObjectSupport這個(gè)類實(shí)現(xiàn)來(lái)的,當(dāng)spring容器啟動(dòng)時(shí)會(huì)初始化這些方法,那我們看看AbstractHandlerMapping的initApplicationContext方法干了啥,貼代碼:


這三個(gè)方法都和initInterceptor有關(guān),第一個(gè)方法extendInterceptors是個(gè)模板方法,目前在子類中并沒有實(shí)現(xiàn),而第二個(gè)字面意思是查找映射過的攔截器主要作用是用來(lái)查找已經(jīng)配置自定義的攔截器和spring自己的攔截器,似乎關(guān)系不大,而下面這個(gè)方法是初始化攔截器,似乎也和handler沒關(guān)系,那我們現(xiàn)在知道了,父類AbstractHandlerMapping作為頂級(jí)定義,他的初始化方法只加載了攔截器,其實(shí)換個(gè)角度想想也是,攔截器是通用的,而不同的HandlerMapping實(shí)現(xiàn)的策略肯定不一樣,不然抽象出AbstractHandlerMapping就沒有意義,那我們接著看看SimpleUrlHanlerMapping重寫了initApplicationContext方法是做了什么事情.


首先,他上來(lái)二話不說先調(diào)用了父類的方法,加載了攔截器的信息,接下來(lái)這個(gè)registerHandlers似乎是重點(diǎn),register是注冊(cè)的意思,他似乎要開始注冊(cè)handler了,看下代碼


他內(nèi)部維護(hù)了一個(gè)map,當(dāng)注冊(cè)handler的時(shí)候他就去遍歷這個(gè)map,map的key是url,object就是Handler,那這個(gè)map是怎么來(lái)的,這就要說到SimpleUrlHandlerMapping的用法,如果配了用SimpleUrlHandlerMapping,那么control需要繼承AbstractController方法,里面有個(gè)handleRequestInternal方法需要實(shí)現(xiàn),再在配置文件中加上請(qǐng)求的路徑和對(duì)應(yīng)的類,這個(gè)就是map的由來(lái),

一個(gè)control只有一個(gè)方法,但是一個(gè)方法可以有多個(gè)url,然后接著看registerHandler方法,這是父類的方法,也就是抽象出來(lái)公有的方法,我們看下


這個(gè)方法很重要,他是先根據(jù)handler的名字去容器中找到這個(gè)對(duì)象,然后在從handlerMap中去根據(jù)url取這個(gè)handler,如果說url里面有這個(gè)handler,就將根據(jù)名字取的和根據(jù)url取的進(jìn)行對(duì)比,看是不是同一個(gè),如果不是同一個(gè)則拋異常,如果說根據(jù)url取的handler是空的,說明還沒有注冊(cè),他又判斷了這個(gè)url是不是"/"這個(gè)請(qǐng)求,如果是則將這個(gè)傳進(jìn)來(lái)的handler設(shè)置為根handler,如果url等于"/*",他又將這個(gè)handler設(shè)置為默認(rèn)的handler,如果都不是就將他放進(jìn)這個(gè)urlMap當(dāng)中,就是將url和handler進(jìn)行注冊(cè),將來(lái)可以通過getHandler方法,取到handler.

2.BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping內(nèi)部就一個(gè)方法,那他的初始化方法肯定不是自己實(shí)現(xiàn)了,而是繼承了父類的方法,我們就看下AbstractDetectingUrlHandlerMapping類

結(jié)構(gòu)和SimpleUrlHandlerMapping方法類似,也是先調(diào)用了父類的initApplicationContext()方法,加載攔截器,然后調(diào)用自己的detectHandlers方法

重點(diǎn)看下這個(gè)方法


他首先是獲取了整個(gè)容器中所有的bean對(duì)象,然后去遍歷這些bean,在遍歷bean的時(shí)候,他有個(gè)方法determineUrlsForHandler,這個(gè)方法正是BeanNameUrlHandlerMapping唯一的方法


BeanNameUrlHandlerMapping是控制器的name作為url,所以在這個(gè)方法里,他先判斷了這個(gè)bean的名字是不是已"/"開頭,如果不是的話又去獲取他的別名,遍歷他的別名,判斷是不是以"/"開頭,最后將最終結(jié)果返回給detectHandlers方法,最后又調(diào)用了registerHandler,也就是上面我們分析的AbstractHandlerMapping的registerHandler的方法,我們看下BeanNameUrlHandlerMapping的用法:

他在control層和SimpleUrlHandlerMapping一樣都需要繼承AbstractController,不同的是配置文件


也可以在此配置他的別名,將來(lái)別名和本名就是兩個(gè)url通過一個(gè)control處理

3.RequestMappingHandlerMapping

這個(gè)組件也就是我們常用的根據(jù)@Controller和@RequestMapping來(lái)映射,內(nèi)部比上面兩個(gè)較復(fù)雜,他是實(shí)現(xiàn)了InitializingBean接口,當(dāng)這個(gè)bean被創(chuàng)建的時(shí)候,會(huì)調(diào)用InitializingBean的afterPropertiesSet方法,這個(gè)方法也是加載RequestMappingHandlerMapping的入口


config對(duì)象主要是用于根據(jù)@Request對(duì)象里面不同的屬性,找到相對(duì)應(yīng)的handler,暫且可以放后面說,先說下super.afterPropertiesSet()方法,該方法又調(diào)用了initHandlerMethods


該方法的執(zhí)行過程和BeanNameUrlHandlerMapping有異曲同工之妙,他首先也是獲取了整個(gè)容器中的bean對(duì)象,然后去判斷是不是handler,我們看下isHandler方法


也就說他判斷從spring容器取出bean是不是handler條件就是這個(gè)對(duì)象有沒有Control和RequstMapping這兩個(gè)注解,如果他是handler,他又根據(jù)這個(gè)handler去查找他的方法,之前的hanlder都是對(duì)象級(jí)別的,因?yàn)榫鸵粋€(gè)方法,而RequestMappingHandlerMapping實(shí)現(xiàn)了handler可以是方法級(jí)別的,

g

這里主要做的就是根據(jù)這個(gè)對(duì)象找到能夠作為handler的方法然后放進(jìn)一個(gè)map當(dāng)中,其中key就是根據(jù)@RequestMapping中的value封裝成的RequestMappingInfo對(duì)象,他的key會(huì)被RequestMappingInfo存在patternsCondition 當(dāng)中,可以看下結(jié)構(gòu):


緊接著遍歷這些方法,重點(diǎn)是registerHandlerMethod這個(gè)方法


它讓mappingRegisterry調(diào)用了他的register方法.先看下mappingRegisterry是個(gè)啥玩意


他的內(nèi)部也是一個(gè)個(gè)鍵值的對(duì)象,然后我們接著看下register方法做了啥


讀寫鎖這個(gè)東西先不說,首先他是把handler和method封裝成了一個(gè)HandlerMethod對(duì)象,

然后直接將mapping和handlerMethod放入一個(gè)map當(dāng)中,此時(shí)的mapping還是剛剛說的RequestMappingInfo對(duì)象,緊接著他就去遍歷了mapping里面的patternsCondition的值,也就是@RequestMapping的url,然后在去遍歷這個(gè)url值,將url和mapping添加到urlLookup當(dāng)中,緊接著又做了一個(gè)命名策略的事,就是將類中的大寫字母提取出來(lái)后面緊跟#號(hào)再加方法的名字,


addMappingName就是把這個(gè)name為鍵,這個(gè)類和方法名放入到nameLookUp當(dāng)中去


在接著就是處理了@CrossOrigin注解,這個(gè)注解是springmvc-4.2新加的功能,.主要是解決跨域問題,和我們分析的關(guān)系不大最后就是將剛剛說的幾個(gè)map封裝成了一個(gè)類,以@RequestMapping的url為值放到了一個(gè)map當(dāng)中,至此,整個(gè)加載過程分析結(jié)束

RequestMappingHandlerMapping功能很強(qiáng)大,在設(shè)計(jì)上也會(huì)相對(duì)來(lái)說比較繁瑣,這里面省略了很多細(xì)節(jié)的東西,比如根據(jù)@Request里面的值,創(chuàng)建不同的對(duì)象以便將來(lái)用相應(yīng)的策略來(lái)查找handler等等.

二.查找handler

上面說過了springmvc是如何加載handler以及spring容器是如何初始化handler的,現(xiàn)在說一說springmvc是如何查找handler的.

查找的入口在DispatcherServlet的doDispatch方法當(dāng)中


我們看下getHandler方法


這里他實(shí)際上去遍歷了之前加載的handler組件,然后查找那一個(gè)handler能狗組裝出執(zhí)行鏈,這個(gè)方法,在父類AbstractHandlerMapping中有定義


其中g(shù)etHandlerInternal方法也是個(gè)模版方法,各個(gè)子類實(shí)現(xiàn)的方法不同,所以由他們提供而返回的handlerChain里面則封裝了具體的handler和攔截器,將來(lái)可以在執(zhí)行具體的方法之前執(zhí)行攔截器的方法,就達(dá)到了攔截的效果,最核心的還是getHandlerInternal這個(gè)方法,獲取handler我們還是以剛剛的三個(gè)類分析,但是可以分為兩種,beanNameUrlHandlerMapping和SimpleUrlHandlerMapping都是繼承AbstractUrlHandlerMapping,所以實(shí)現(xiàn)由他實(shí)現(xiàn),我們看下他的方法

1.AbstractUrlHandlerMapping


他顯示從request中取到訪問請(qǐng)求的路徑,在從之前添加的map當(dāng)中去找這個(gè)handler,緊接著又根據(jù)url判斷了一些情況,基本和添加的思路相反,不多說.

2.RequestMappingHandlerMapping

他的查找方法來(lái)自于AbstractHandlerMethodMapping方法

他的邏輯首先也是獲取到url,然后在根據(jù)url去獲取handler,其中主要是lookupHandlerMethod這個(gè)方法


他在這里主要做的事情就是根據(jù)之前添加進(jìn)去的url和handler找到合適的handler,如url添加的是"/test/{id}",他會(huì)去找到合適的請(qǐng)求,這里就不細(xì)說了,有興趣可以研究研究

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末揩晴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖骤视,帶你破解...
    沈念sama閱讀 211,496評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辖试,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡田绑,警方通過查閱死者的電腦和手機(jī)嘁信,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,187評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門于样,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人潘靖,你說我怎么就攤上這事穿剖。” “怎么了秘豹?”我有些...
    開封第一講書人閱讀 157,091評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵携御,是天一觀的道長(zhǎng)昌粤。 經(jīng)常有香客問我既绕,道長(zhǎng),這世上最難降的妖魔是什么涮坐? 我笑而不...
    開封第一講書人閱讀 56,458評(píng)論 1 283
  • 正文 為了忘掉前任凄贩,我火速辦了婚禮,結(jié)果婚禮上袱讹,老公的妹妹穿的比我還像新娘疲扎。我一直安慰自己,他們只是感情好捷雕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,542評(píng)論 6 385
  • 文/花漫 我一把揭開白布椒丧。 她就那樣靜靜地躺著,像睡著了一般救巷。 火紅的嫁衣襯著肌膚如雪壶熏。 梳的紋絲不亂的頭發(fā)上愕宋,一...
    開封第一講書人閱讀 49,802評(píng)論 1 290
  • 那天翰意,我揣著相機(jī)與錄音,去河邊找鬼烂瘫。 笑死精盅,一個(gè)胖子當(dāng)著我的面吹牛帽哑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叹俏,決...
    沈念sama閱讀 38,945評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼妻枕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起屡谐,我...
    開封第一講書人閱讀 37,709評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鹰贵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后康嘉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碉输,經(jīng)...
    沈念sama閱讀 44,158評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,502評(píng)論 2 327
  • 正文 我和宋清朗相戀三年亭珍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了敷钾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,637評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肄梨,死狀恐怖阻荒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情众羡,我是刑警寧澤侨赡,帶...
    沈念sama閱讀 34,300評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站粱侣,受9級(jí)特大地震影響羊壹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜齐婴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,911評(píng)論 3 313
  • 文/蒙蒙 一油猫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧柠偶,春花似錦情妖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,744評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蔫仙,卻和暖如春料睛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背匀哄。 一陣腳步聲響...
    開封第一講書人閱讀 31,982評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工秦效, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涎嚼。 一個(gè)月前我還...
    沈念sama閱讀 46,344評(píng)論 2 360
  • 正文 我出身青樓阱州,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親法梯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子苔货,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,500評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理犀概,服務(wù)發(fā)現(xiàn),斷路器夜惭,智...
    卡卡羅2017閱讀 134,632評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,768評(píng)論 25 707
  • 引言 一直以來(lái)都在使用Spring mvc姻灶,能夠熟練使用它的各種組件。但是诈茧,它一直像個(gè)黑盒一樣产喉,我并不知道它內(nèi)部是...
    yoqu閱讀 905評(píng)論 0 24
  • 回顧2014年發(fā)現(xiàn),80歲的Alice Gerrard發(fā)新磚了敢会。陌生的名字曾沈,她是誰(shuí)?她的歌好聽嗎鸥昏? AMG上Ger...
    loveisbug閱讀 224評(píng)論 0 2
  • 并非因?yàn)闅q月泥濘才無(wú)處落腳 暖陽(yáng)僅僅是又一次冰霜的預(yù)演 在死囚淪為陪審之前 相聚早已淪為 未經(jīng)判決的流亡 幽默足夠...
    Stellavallis閱讀 250評(píng)論 0 0