SpringMVC工作原理之處理映射[HandlerMapping]

請求過來是怎么映射到對應(yīng)的方法上碑韵,這里離不開映射處理器 HandlerMapping乏盐,今天這篇筆記就來探究 HandlerMapping 實現(xiàn)邏輯保屯。
本篇筆記主要分析SpringMVC 5.1.1 這個版本辽俗。

SpringMVC運行流程

SpringMVC 內(nèi)部是根據(jù) HandlerMappingRequestController 里面的方法對應(yīng)起來的深啤,為了方便理解础废,我這里把實現(xiàn)它的子類統(tǒng)稱為映射處理器[ps: 自己一時興起瞎起的汛骂,不準(zhǔn)確還請見諒]。
HandlerMapping 功能就是根據(jù)請求匹配到對應(yīng)的 Handler评腺,然后將找到的 Handler 和所有匹配的 HandlerInterceptor(攔截器)綁定到創(chuàng)建的 HandlerExecutionChain 對象上并返回帘瞭。
HandlerMapping 只是一個接口類,不同的實現(xiàn)類有不同的匹對方式蒿讥,根據(jù)功能的不同我們需要在 SpringMVC 容器中注入不同的映射處理器 HandlerMapping蝶念。

簡單工作圖如下

HandlerMapping.jpg

1 HandlerMapping 接口

1.1 HandlerMapping 注入

DispatcherServlet 類中有下面這個方法

public class DispatcherServlet extends FrameworkServlet {
  private void initHandlerMappings(ApplicationContext context) {...}
}

容器被初始化的時候會被調(diào)用,加載容器中注入的 HandlerMapping芋绸。其實常用到的 HandlerMapping 都是由 <mvc:annotation-driven /> 標(biāo)簽幫我們注冊的(包括 RequestMappingHandlerMappingBeanNameUrlHandlerMapping)媒殉,如果沒有寫該標(biāo)簽系統(tǒng)也會幫我們注入默認(rèn)的映射器,當(dāng)然也有些需要我們自己手動注入摔敛。

1.2 HandlerExecutionChain 初始化

DispatcherServlet 類中廷蓉,doDispatch(..) 方法總通過調(diào)用本類的 getHandler(..) 方法得到 HandlerExecutionChain 對象。

看到這里肯定很模糊马昙,具體 HandlerMapping 內(nèi)部通過調(diào)用 getHandler(..) 得到 HandlerExecutionChain 對象細(xì)節(jié)請往下看苦酱。

1.3 HandlerMapping 接口

HandlerMapping 接口中只有一個方法

首先我們來看下實現(xiàn)類結(jié)構(gòu)

展開細(xì)看

大致上分為兩大類 AbstractUrlHandlerMappingAbstractHandlerMethodMapping售貌。都繼承自 AbstractHandlerMapping 抽象類,實現(xiàn) HandlerMapping 接口疫萤。

2 HandlerMapping 接口實現(xiàn)抽象類 AbstractHandlerMapping

AbstractHandlerMapping 類中實現(xiàn) getHandler(..) 接口方法得到 HandlerExecutionChain 對象

同時 AbstractHandlerMapping 繼承 WebApplicationObjectSupport,初始化時會自動調(diào)用模板方法 initApplicationContext

2.1 AbstractHandlerMapping 實現(xiàn)類分支之一 AbstractUrlHandlerMapping

AbstractUrlHandlerMapping:URL 映射的抽象基類敢伸,提供將處理程序映射到 Controller扯饶,所以該類最終直接返回的 handler 就是 Controller 對象臼疫。

實現(xiàn)父抽象類的抽象方法 getHandlerInternal(..) 匹配并返回對應(yīng)的 Handler 對象民晒。

接下來咱們看看根據(jù)路徑匹對 handler 的方法 lookupHandler(..)

上面代碼可以看出從 this.handlerMap 中通過 urlPath 匹對找到對應(yīng)的 handler 對象。那接下來就看下開始是怎么將 handler 對象加入到 this.handlerMap 集合中是關(guān)鍵迷郑。

那接下來調(diào)研這個 protected void registerHandler(String urlPath, Object handler) {} 這個方法什么時候調(diào)用躯砰,怎么調(diào)用就是接下來的重點了每币。
從源碼來看是在 AbstractUrlHandlerMapping 子類里面調(diào)用。

AbstractUrlHandlerMapping 的子類從上面截圖的類結(jié)構(gòu)可以看出來琢歇,大致分為兩類:

  • 間接繼承 AbstractUrlHandlerMappingBeanNameUrlHandlerMapping
  • 直接繼承 AbstractUrlHandlerMappingSimpleUrlHandlerMapping
2.1.1 BeanNameUrlHandlerMapping

首先來看其父類 AbstractDetectingUrlHandlerMapping 怎么調(diào)用 registerHandler(String urlPath, Object handler) 又怎么匹配到配置在容器中的 handler 并將其注入到 AbstractUrlHandlerMappingthis.handlerMap 中兰怠。

接下來看下 BeanNameUrlHandlerMapping 里面的 determineUrlsForHandler(..) 方法是怎么實現(xiàn)匹對 beanName 是否跟該映射器相關(guān)并返回 URLs 的邏輯吧。

從上面的源碼分析我們可以得知李茫,在 SpringMVC 容器中揭保,且在注入了 BeanNameUrlHandlerMapping 映射器的時候,只要是以 "/" 開頭的 bean 的 name魄宏,都會作為該映射器匹配的 Handler 對象秸侣。

接下來咱們就自定義一個經(jīng)過該映射器匹對的視圖,但是在自定義之前我們需要先了解下 Controller 這個接口宠互。因為使用 AbstractUrlHandlerMapping 的實現(xiàn)類時味榛,需要讓控制層的類實現(xiàn) Controller 接口(一般繼承 AbstractController 即可),另外還有一些已經(jīng)實現(xiàn)了的 Controller 類予跌,如下圖所示搏色。但是不論是自己實現(xiàn) Controller 接口還是繼承系統(tǒng)已經(jīng)實現(xiàn)的類,都只能處理一個請求匕得。

首先編寫控制層代碼

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloController extends AbstractController {

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("訪問方法進(jìn)來了");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("success");
        return mv;
    }
}

接下面在 SpringMVC 容器中注入 BeanNameUrlHandlerMapping 映射器和自定義的 Controller

<!-- 注冊 HandlerMapping -->
<!--<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>-->

<!-- <mvc:annotation-driven /> 自動幫我們注入 BeanNameUrlHandlerMapping 映射器, 所以與上面手動注入該映射器選其一就行  -->
<mvc:annotation-driven />

<!-- 注冊 Handler -->
<bean id="/hello" class="com.ogemray.urlHandlerMapping.HelloController"></bean>

注意手動注入 BeanNameUrlHandlerMapping 映射器記得不要跟 <mvc:annotation-driven /> 標(biāo)簽自動幫我們注入重復(fù)(如自己手動注入要么放在 <mvc:annotation-driven /> 標(biāo)簽之前继榆,要么直接不寫),不然重復(fù)注冊兩次該映射器雖說沒有大的影響汁掠,但是也有點浪費內(nèi)存沒必要略吨。
注意自定義 Controller 實現(xiàn)類注入 beanidname 必須以 "/" 開頭,因為上面分析源碼說過考阱,BeanNameUrlHandlerMapping 映射器主要映射以 "/" 開頭的 beanName翠忠。

2.1.2 SimpleUrlHandlerMapping

在接下來咱們看看該映射器是怎么調(diào)用父類的 registerHandler(String urlPath, Object handler) 方法將 handler 加進(jìn) AbstractUrlHandlerMappingthis.handlerMap 中。

從上面源碼可以看出 SimpleUrlHandlerMapping 映射器跟前面 BeanNameUrlHandlerMapping 映射器有點不一樣乞榨。后者是有點類似遍歷容器里面有所的 beannameid 找到匹配的秽之,并且 beannameid 有特殊要求当娱,匹配的則加入。而前者則是先將加入該映射器的 handler 先加進(jìn)該映射器的一個集合屬性里面考榨,容器初始化的時候免去了遍歷麻煩的步驟跨细。

接下來咱們就自定義一個經(jīng)過該映射器匹對的視圖。

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloController extends AbstractController {

    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("訪問方法進(jìn)來了");
        ModelAndView mv = new ModelAndView();
        mv.setViewName("success");
        return mv;
    }
}

接下面在 Spring MVC 容器中注入 SimpleUrlHandlerMapping 映射器和自定義的 Controller

<!-- 注冊 HandlerMapping -->
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
        <props>
            <prop key="/hello">helloController</prop>
        </props>
    </property>
</bean>
<!-- 注冊 Handler -->
<bean id="helloController" class="com.ogemray.urlHandlerMapping.HelloController"></bean>

2.2 AbstractHandlerMapping 實現(xiàn)類分支之二 AbstractHandlerMethodMapping

AbstractHandlerMethodMapping 最終獲取的 handlerHandlerMethod 類型對象河质。實現(xiàn)該抽象類的子類映射器具體映射什么樣的方法冀惭,怎么實現(xiàn)請求和方法的映射,該抽象類都給出了抽象接口掀鹅,可高度自定義[ps: 這里給我印象很深散休,學(xué)到了]。

接下來看下該類的實現(xiàn)鏈:

具體實現(xiàn)類只有一個 RequestMappingHandlerMapping乐尊,在開始下面正式邏輯分析之前戚丸,我們需要了解幾個類。

2.2.1 簡單了解 HandlerMethod

HandlerMethod 其實可以簡單理解為保持方法信息的pojo類

2.2.2 RequestMappingInfo

主要用來記錄方法上 @RequestMapping() 注解里面的參數(shù)扔嵌,針對 RequestMappingHandlerMapping 映射器來使用限府。

在容器初始化過程中創(chuàng)建映射器(RequestMappingHandlerMapping)對象時,會尋找所有被@Controller 注解類中被 @RequestMapping 注解的方法对人,然后解析方法上的 @RequestMapping 注解谣殊,把解析結(jié)果封裝成 RequestMappingInfo 對象,也就是說RequestMappingInfo 對象是用來裝載方法的匹配相關(guān)信息牺弄,每個匹配的方法都會對應(yīng)一個 RequestMappingInfo 對象∫黾福現(xiàn)在大家應(yīng)該能明白 RequestMappingInfo 的作用了吧。

  • PatternsRequestCondition:模式請求路徑過濾器势告,對應(yīng)記錄和判斷 @RequestMapping 注解上的 value 屬性蛇捌。
  • RequestMethodsRequestCondition:請求方法過濾器,對應(yīng)記錄和判斷 @RequestMapping 注解上的 method 屬性咱台。
  • ParamsRequestCondition:請求參數(shù)過濾器络拌,對應(yīng)記錄和判斷 @RequestMapping 注解上的 params 屬性。
  • HeadersRequestCondition:頭字段過濾器回溺,對應(yīng)記錄和判斷 @RequestMapping 注解上的 headers 屬性春贸。
  • ConsumesRequestCondition:請求媒體類型過濾器,對應(yīng)記錄和判斷 @RequestMapping 注解上的 consumes 屬性遗遵。
  • ProducesRequestCondition:應(yīng)答媒體類型過濾器萍恕,對應(yīng)記錄和判斷 @RequestMapping 注解上的 produces 屬性。
  • RequestConditionHolder:預(yù)留自定義擴展過濾器车要。
2.2.3 進(jìn)入 AbstractHandlerMethodMapping 映射器內(nèi)部

① 首先來看下該類實現(xiàn)父抽象類(AbstractHandlerMapping) 的抽象方法 getHandlerInternal(..) 匹配并返回對應(yīng)的 handler 對象允粤。

跟前面的另一個實現(xiàn)分支 AbstractUrlHandlerMapping 實現(xiàn)看起來差不多,都是根據(jù)請求路徑來匹對,但是內(nèi)部配對方式有什么不同還需要我們接著往下看类垫。

注意:

  • Match 就是該抽象類里面自定義的一個內(nèi)部類司光,用來記錄方法標(biāo)記信息對象 mapping 和方法源信息對象 HandlerMethod
  • 當(dāng)請求為 restful 風(fēng)格時悉患,將會遍歷所有的 mapping残家,然后一個個匹對,非常耗時和費資源购撼。優(yōu)化請參考 springMVC在restful風(fēng)格的性能優(yōu)化
  • 上面的兩個抽象方法(getMatchingMapping(..)getMappingComparator(..))
    前者要實現(xiàn)檢查提供的請求映射信息中的條件是否與請求匹配跪削。
    后者要實現(xiàn)當(dāng)一個 Request 對應(yīng)多個 mapping 時的擇優(yōu)方案。

② 看下存儲映射關(guān)系對象(MappingRegistry)內(nèi)部結(jié)構(gòu)
說到這里迂求,可能大家對于這個 this.mappingRegistery 對象十分好奇,里面到底是怎么存儲數(shù)據(jù)的晃跺,先是可以根據(jù) lookupPath 找到 List<mapping>揩局,接著后來又根據(jù) mapping 找到 HandlerMethod 對象。

該實體類里面最重要的兩個記錄集合分別是 mappingLookupurlLookup 掀虎。

  • urlLookup:主要用來記錄 lookupPath 請求路徑對應(yīng)的 mapping 集合凌盯。
    這里 Spring 留了一個很活的機制,拿 @RequestMapping 注解來說烹玉,他的 value 屬性本身就是一個字符數(shù)組驰怎,在多重設(shè)置中難免有路徑重復(fù)的,所以最終有可能會出現(xiàn)一個 lookupPath 對應(yīng)多個 RequestMappingInfo二打,最終在請求過來的時候給了自定義抽象方法讓實現(xiàn)類自己實現(xiàn)擇優(yōu)的方式县忌。
    MutivalueMap 是 SpringMVC 自定義的一個 Map 類,key 對應(yīng)的 value 是一個集合继效,這從名字上也能看出來症杏。

  • mappingLookup:key 是 mapping 對象,value 是 HandlerMethod 對象瑞信,最終是通過 lookupPathurlLookup 集合中找到對應(yīng)的 mapping 對象厉颤,通過 mappingmappingLookup 集合中找到 HandlerMethod 對象。

③ 看下是怎么將映射關(guān)系裝進(jìn)緩存(MappingRegistry) 對象中的

容器初始化的時候都干了些什么

isHandler(..) 是該抽象類定義的抽象方法凡简,由實現(xiàn)類自己去實現(xiàn)匹對哪些類逼友。看下 RequestMappingHandlerMapping 映射器是怎么實現(xiàn)的吧

看來 RequestMappingHandlerMapping 映射器秤涩,只要類上有 ControllerRequestMapping 注解帜乞,就符合該映射器管轄范圍。

接著解析往下看

來個分支看下 RequestMappingHandlerMapping 是怎么實現(xiàn)抽象方法 getMappingForMethod(..) 方法的溉仑,該映射器都匹配什么樣的方法呢挖函?

猜也能猜到,RequestMappingHandlerMapping 映射器肯定匹配有 @RequestMapping 注解的方法,并返回該方法的映射信息對象 RequestMappingInfo 對象怨喘。

下面就到了最后一步津畸,具體這個映射關(guān)系是怎么裝入映射器的 MappingRegistry 對象屬性的緩存的呢?

3 總結(jié)

到這里必怜,關(guān)于 SpringMVC 內(nèi)部是怎么通過 HandlerMapping 映射器將各自對應(yīng)映射的資源在容器初始的時候裝到自身的緩存肉拓,在請求過來時又是怎么找到對應(yīng)的資源返回最終對應(yīng)的 handler 對象已經(jīng)描述完了。

現(xiàn)在開發(fā)我們基本都不用 AbstractUrlHandlerMapping 這種類型的映射器了梳庆,但是 SpringMVC 內(nèi)部還有用到的地方暖途,例如直接 <mvc:view-controller path="" view-name=""/> 標(biāo)簽配置資源不經(jīng)過視圖控制器直接跳轉(zhuǎn)就用到了 SimpleUrlHandlerMapping 這種映射器。AbstractUrlHandlerMapping 匹對解析對應(yīng)請求最終返回的 handlerController 對象膏执。
現(xiàn)在我們習(xí)慣直接用 @Controller@RequestMapping 這樣注解來描述視圖控制器的邏輯驻售,這種資源映射用的是 AbstractHandlerMethodMapping 抽象類的子類 RequestMappingHandlerMapping 映射器,匹對解析對應(yīng)的請求返回HandlerMethod 對象更米。

通過研究這種映射欺栗,對于我個人來說學(xué)到了很多,優(yōu)秀的設(shè)計模式遵循開閉原則征峦,擴展放開修改關(guān)閉迟几,高度模塊化同時也支持高度自定義話,優(yōu)秀@赴省@嗳!

其他相關(guān)文章

SpringMVC入門筆記
SpringMVC工作原理之處理映射[HandlerMapping]
SpringMVC工作原理之適配器[HandlerAdapter]
SpringMVC工作原理之參數(shù)解析
SpringMVC之自定義參數(shù)解析
SpringMVC工作原理之視圖解析及自定義
SpingMVC之<mvc:annotation-driven/>標(biāo)簽

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛉加,一起剝皮案震驚了整個濱河市蚜枢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌七婴,老刑警劉巖祟偷,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異打厘,居然都是意外死亡修肠,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門户盯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嵌施,“玉大人,你說我怎么就攤上這事莽鸭÷鹕耍” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵硫眨,是天一觀的道長足淆。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么巧号? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任族奢,我火速辦了婚禮,結(jié)果婚禮上丹鸿,老公的妹妹穿的比我還像新娘越走。我一直安慰自己,他們只是感情好靠欢,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布廊敌。 她就那樣靜靜地躺著,像睡著了一般门怪。 火紅的嫁衣襯著肌膚如雪骡澈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天掷空,我揣著相機與錄音秧廉,去河邊找鬼。 笑死拣帽,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嚼锄。 我是一名探鬼主播减拭,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼区丑!你這毒婦竟也來了拧粪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤沧侥,失蹤者是張志新(化名)和其女友劉穎可霎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宴杀,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡癣朗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了旺罢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旷余。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖扁达,靈堂內(nèi)的尸體忽然破棺而出正卧,到底是詐尸還是另有隱情,我是刑警寧澤跪解,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布炉旷,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏窘行。R本人自食惡果不足惜饥追,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抽高。 院中可真熱鬧判耕,春花似錦、人聲如沸翘骂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽碳竟。三九已至草丧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莹桅,已是汗流浹背昌执。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留诈泼,地道東北人懂拾。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像铐达,于是被迫代替她去往敵國和親岖赋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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

  • 1. 配置 springMVC的核心是DispatcherServlet瓮孙,它實現(xiàn)了HttpServlet類唐断,在we...
    aaron1993閱讀 1,287評論 0 0
  • 對于java中的思考的方向,1必須要看前端的頁面杭抠,對于前端的頁面基本的邏輯脸甘,如果能理解最好,不理解也要知道幾點偏灿。 ...
    神尤魯?shù)婪?/span>閱讀 819評論 0 0
  • HandlerMapping的類繼承圖如下: 其中有一個DefaultAnnotationHanlerMappin...
    宙斯是只貓閱讀 3,372評論 0 7
  • 引言 一直以來都在使用Spring mvc丹诀,能夠熟練使用它的各種組件。但是菩混,它一直像個黑盒一樣忿墅,我并不知道它內(nèi)部是...
    yoqu閱讀 912評論 0 24
  • 一開始畫很害怕,怕自己畫不好于是堅持每日一畫沮峡,漸漸的找到了感覺愿前輩們多指點
    顧李李閱讀 279評論 4 4