請求過來是怎么映射到對應(yīng)的方法上碑韵,這里離不開映射處理器
HandlerMapping
乏盐,今天這篇筆記就來探究HandlerMapping
實現(xiàn)邏輯保屯。
本篇筆記主要分析SpringMVC 5.1.1 這個版本辽俗。
SpringMVC 內(nèi)部是根據(jù) HandlerMapping
將 Request
和 Controller
里面的方法對應(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
蝶念。
簡單工作圖如下
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)簽幫我們注冊的(包括 RequestMappingHandlerMapping
和 BeanNameUrlHandlerMapping
)媒殉,如果沒有寫該標(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ì)看
大致上分為兩大類 AbstractUrlHandlerMapping
和 AbstractHandlerMethodMapping
售貌。都繼承自 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)可以看出來琢歇,大致分為兩類:
- 間接繼承
AbstractUrlHandlerMapping
的BeanNameUrlHandlerMapping
- 直接繼承
AbstractUrlHandlerMapping
的SimpleUrlHandlerMapping
2.1.1 BeanNameUrlHandlerMapping
首先來看其父類 AbstractDetectingUrlHandlerMapping
怎么調(diào)用 registerHandler(String urlPath, Object handler)
又怎么匹配到配置在容器中的 handler
并將其注入到 AbstractUrlHandlerMapping
的 this.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)類注入 bean
的 id
或 name
必須以 "/" 開頭,因為上面分析源碼說過考阱,BeanNameUrlHandlerMapping
映射器主要映射以 "/" 開頭的 beanName翠忠。
2.1.2 SimpleUrlHandlerMapping
在接下來咱們看看該映射器是怎么調(diào)用父類的 registerHandler(String urlPath, Object handler)
方法將 handler
加進(jìn) AbstractUrlHandlerMapping
的 this.handlerMap
中。
從上面源碼可以看出 SimpleUrlHandlerMapping
映射器跟前面 BeanNameUrlHandlerMapping
映射器有點不一樣乞榨。后者是有點類似遍歷容器里面有所的 bean
的 name
或 id
找到匹配的秽之,并且 bean
的 name
或 id
有特殊要求当娱,匹配的則加入。而前者則是先將加入該映射器的 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
最終獲取的 handler
是 HandlerMethod
類型對象河质。實現(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
對象。
該實體類里面最重要的兩個記錄集合分別是 mappingLookup
和 urlLookup
掀虎。
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
對象瑞信,最終是通過lookupPath
在urlLookup
集合中找到對應(yīng)的mapping
對象厉颤,通過mapping
在mappingLookup
集合中找到HandlerMethod
對象。
③ 看下是怎么將映射關(guān)系裝進(jìn)緩存(MappingRegistry
) 對象中的
容器初始化的時候都干了些什么
isHandler(..)
是該抽象類定義的抽象方法凡简,由實現(xiàn)類自己去實現(xiàn)匹對哪些類逼友。看下 RequestMappingHandlerMapping
映射器是怎么實現(xiàn)的吧
看來 RequestMappingHandlerMapping
映射器秤涩,只要類上有 Controller
或 RequestMapping
注解帜乞,就符合該映射器管轄范圍。
接著解析往下看
來個分支看下 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)請求最終返回的 handler
是 Controller
對象膏执。
現(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)簽