請求過來是怎么映射到對應的方法上,這里離不開映射處理器
HandlerMapping,今天這篇筆記就來探究HandlerMapping實現邏輯。
本篇筆記主要分析SpringMVC 5.1.1 這個版本。

SpringMVC 內部是根據 HandlerMapping 將 Request 和 Controller 里面的方法對應起來的,為了方便理解,我這里把實現它的子類統(tǒng)稱為映射處理器[ps: 自己一時興起瞎起的,不準確還請見諒]。
HandlerMapping 功能就是根據請求匹配到對應的 Handler,然后將找到的 Handler 和所有匹配的 HandlerInterceptor(攔截器)綁定到創(chuàng)建的 HandlerExecutionChain 對象上并返回。
HandlerMapping 只是一個接口類,不同的實現類有不同的匹對方式,根據功能的不同我們需要在 SpringMVC 容器中注入不同的映射處理器 HandlerMapping。
簡單工作圖如下

1 HandlerMapping 接口
1.1 HandlerMapping 注入
在 DispatcherServlet 類中有下面這個方法
public class DispatcherServlet extends FrameworkServlet {
private void initHandlerMappings(ApplicationContext context) {...}
}
容器被初始化的時候會被調用,加載容器中注入的 HandlerMapping。其實常用到的 HandlerMapping 都是由 <mvc:annotation-driven /> 標簽幫我們注冊的(包括 RequestMappingHandlerMapping 和 BeanNameUrlHandlerMapping),如果沒有寫該標簽系統(tǒng)也會幫我們注入默認的映射器,當然也有些需要我們自己手動注入。
1.2 HandlerExecutionChain 初始化
在 DispatcherServlet 類中,doDispatch(..) 方法總通過調用本類的 getHandler(..) 方法得到 HandlerExecutionChain 對象。

看到這里肯定很模糊,具體 HandlerMapping 內部通過調用 getHandler(..) 得到 HandlerExecutionChain 對象細節(jié)請往下看。
1.3 HandlerMapping 接口
在 HandlerMapping 接口中只有一個方法

首先我們來看下實現類結構

展開細看

大致上分為兩大類 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping。都繼承自 AbstractHandlerMapping 抽象類,實現 HandlerMapping 接口。
2 HandlerMapping 接口實現抽象類 AbstractHandlerMapping
在 AbstractHandlerMapping 類中實現 getHandler(..) 接口方法得到 HandlerExecutionChain 對象

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

2.1 AbstractHandlerMapping 實現類分支之一 AbstractUrlHandlerMapping
AbstractUrlHandlerMapping:URL 映射的抽象基類,提供將處理程序映射到 Controller,所以該類最終直接返回的 handler 就是 Controller 對象。
實現父抽象類的抽象方法 getHandlerInternal(..) 匹配并返回對應的 Handler 對象。

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

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

那接下來調研這個 protected void registerHandler(String urlPath, Object handler) {} 這個方法什么時候調用,怎么調用就是接下來的重點了。
從源碼來看是在 AbstractUrlHandlerMapping 子類里面調用。
AbstractUrlHandlerMapping 的子類從上面截圖的類結構可以看出來,大致分為兩類:
- 間接繼承
AbstractUrlHandlerMapping的BeanNameUrlHandlerMapping - 直接繼承
AbstractUrlHandlerMapping的SimpleUrlHandlerMapping
2.1.1 BeanNameUrlHandlerMapping
首先來看其父類 AbstractDetectingUrlHandlerMapping 怎么調用 registerHandler(String urlPath, Object handler) 又怎么匹配到配置在容器中的 handler 并將其注入到 AbstractUrlHandlerMapping 的 this.handlerMap 中。

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

從上面的源碼分析我們可以得知,在 SpringMVC 容器中,且在注入了 BeanNameUrlHandlerMapping 映射器的時候,只要是以 "/" 開頭的 bean 的 name,都會作為該映射器匹配的 Handler 對象。
接下來咱們就自定義一個經過該映射器匹對的視圖,但是在自定義之前我們需要先了解下 Controller 這個接口。因為使用 AbstractUrlHandlerMapping 的實現類時,需要讓控制層的類實現 Controller 接口(一般繼承 AbstractController 即可),另外還有一些已經實現了的 Controller 類,如下圖所示。但是不論是自己實現 Controller 接口還是繼承系統(tǒ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("訪問方法進來了");
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 /> 標簽自動幫我們注入重復(如自己手動注入要么放在 <mvc:annotation-driven /> 標簽之前,要么直接不寫),不然重復注冊兩次該映射器雖說沒有大的影響,但是也有點浪費內存沒必要。
注意自定義 Controller 實現類注入 bean 的 id 或 name 必須以 "/" 開頭,因為上面分析源碼說過,BeanNameUrlHandlerMapping 映射器主要映射以 "/" 開頭的 beanName。
2.1.2 SimpleUrlHandlerMapping
在接下來咱們看看該映射器是怎么調用父類的 registerHandler(String urlPath, Object handler) 方法將 handler 加進 AbstractUrlHandlerMapping 的 this.handlerMap 中。

從上面源碼可以看出 SimpleUrlHandlerMapping 映射器跟前面 BeanNameUrlHandlerMapping 映射器有點不一樣。后者是有點類似遍歷容器里面有所的 bean 的 name 或 id 找到匹配的,并且 bean 的 name 或 id 有特殊要求,匹配的則加入。而前者則是先將加入該映射器的 handler 先加進該映射器的一個集合屬性里面,容器初始化的時候免去了遍歷麻煩的步驟。
接下來咱們就自定義一個經過該映射器匹對的視圖。
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("訪問方法進來了");
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 實現類分支之二 AbstractHandlerMethodMapping
AbstractHandlerMethodMapping 最終獲取的 handler 是 HandlerMethod 類型對象。實現該抽象類的子類映射器具體映射什么樣的方法,怎么實現請求和方法的映射,該抽象類都給出了抽象接口,可高度自定義[ps: 這里給我印象很深,學到了]。
接下來看下該類的實現鏈:

具體實現類只有一個 RequestMappingHandlerMapping,在開始下面正式邏輯分析之前,我們需要了解幾個類。
2.2.1 簡單了解 HandlerMethod
HandlerMethod 其實可以簡單理解為保持方法信息的pojo類
2.2.2 RequestMappingInfo 類
主要用來記錄方法上 @RequestMapping() 注解里面的參數,針對 RequestMappingHandlerMapping 映射器來使用。
在容器初始化過程中創(chuàng)建映射器(RequestMappingHandlerMapping)對象時,會尋找所有被@Controller 注解類中被 @RequestMapping 注解的方法,然后解析方法上的 @RequestMapping 注解,把解析結果封裝成 RequestMappingInfo 對象,也就是說RequestMappingInfo 對象是用來裝載方法的匹配相關信息,每個匹配的方法都會對應一個 RequestMappingInfo 對象?,F在大家應該能明白 RequestMappingInfo 的作用了吧。

-
PatternsRequestCondition:模式請求路徑過濾器,對應記錄和判斷@RequestMapping注解上的value屬性。 -
RequestMethodsRequestCondition:請求方法過濾器,對應記錄和判斷@RequestMapping注解上的method屬性。 -
ParamsRequestCondition:請求參數過濾器,對應記錄和判斷@RequestMapping注解上的params屬性。 -
HeadersRequestCondition:頭字段過濾器,對應記錄和判斷@RequestMapping注解上的headers屬性。 -
ConsumesRequestCondition:請求媒體類型過濾器,對應記錄和判斷@RequestMapping注解上的consumes屬性。 -
ProducesRequestCondition:應答媒體類型過濾器,對應記錄和判斷@RequestMapping注解上的produces屬性。 -
RequestConditionHolder:預留自定義擴展過濾器。
2.2.3 進入 AbstractHandlerMethodMapping 映射器內部
① 首先來看下該類實現父抽象類(AbstractHandlerMapping) 的抽象方法 getHandlerInternal(..) 匹配并返回對應的 handler 對象。

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

注意:
-
Match就是該抽象類里面自定義的一個內部類,用來記錄方法標記信息對象mapping和方法源信息對象HandlerMethod。 - 當請求為 restful 風格時,將會遍歷所有的 mapping,然后一個個匹對,非常耗時和費資源。優(yōu)化請參考 springMVC在restful風格的性能優(yōu)化
- 上面的兩個抽象方法(
getMatchingMapping(..)和getMappingComparator(..))
前者要實現檢查提供的請求映射信息中的條件是否與請求匹配。
后者要實現當一個Request對應多個mapping時的擇優(yōu)方案。
② 看下存儲映射關系對象(MappingRegistry)內部結構
說到這里,可能大家對于這個 this.mappingRegistery 對象十分好奇,里面到底是怎么存儲數據的,先是可以根據 lookupPath 找到 List<mapping>,接著后來又根據 mapping 找到 HandlerMethod 對象。

該實體類里面最重要的兩個記錄集合分別是 mappingLookup 和 urlLookup 。
urlLookup:主要用來記錄lookupPath請求路徑對應的mapping集合。
這里 Spring 留了一個很活的機制,拿@RequestMapping注解來說,他的value屬性本身就是一個字符數組,在多重設置中難免有路徑重復的,所以最終有可能會出現一個lookupPath對應多個RequestMappingInfo,最終在請求過來的時候給了自定義抽象方法讓實現類自己實現擇優(yōu)的方式。
MutivalueMap是 SpringMVC 自定義的一個Map類,key 對應的 value 是一個集合,這從名字上也能看出來。mappingLookup:key 是mapping對象,value 是HandlerMethod對象,最終是通過lookupPath在urlLookup集合中找到對應的mapping對象,通過mapping在mappingLookup集合中找到HandlerMethod對象。
③ 看下是怎么將映射關系裝進緩存(MappingRegistry) 對象中的
容器初始化的時候都干了些什么

isHandler(..) 是該抽象類定義的抽象方法,由實現類自己去實現匹對哪些類??聪?RequestMappingHandlerMapping 映射器是怎么實現的吧

看來 RequestMappingHandlerMapping 映射器,只要類上有 Controller 或 RequestMapping 注解,就符合該映射器管轄范圍。
接著解析往下看

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

猜也能猜到,RequestMappingHandlerMapping 映射器肯定匹配有 @RequestMapping 注解的方法,并返回該方法的映射信息對象 RequestMappingInfo 對象。
下面就到了最后一步,具體這個映射關系是怎么裝入映射器的 MappingRegistry 對象屬性的緩存的呢?

3 總結
到這里,關于 SpringMVC 內部是怎么通過 HandlerMapping 映射器將各自對應映射的資源在容器初始的時候裝到自身的緩存,在請求過來時又是怎么找到對應的資源返回最終對應的 handler 對象已經描述完了。
現在開發(fā)我們基本都不用 AbstractUrlHandlerMapping 這種類型的映射器了,但是 SpringMVC 內部還有用到的地方,例如直接 <mvc:view-controller path="" view-name=""/> 標簽配置資源不經過視圖控制器直接跳轉就用到了 SimpleUrlHandlerMapping 這種映射器。AbstractUrlHandlerMapping 匹對解析對應請求最終返回的 handler 是 Controller 對象。
現在我們習慣直接用 @Controller 和 @RequestMapping 這樣注解來描述視圖控制器的邏輯,這種資源映射用的是 AbstractHandlerMethodMapping 抽象類的子類 RequestMappingHandlerMapping 映射器,匹對解析對應的請求返回HandlerMethod 對象。
通過研究這種映射,對于我個人來說學到了很多,優(yōu)秀的設計模式遵循開閉原則,擴展放開修改關閉,高度模塊化同時也支持高度自定義話,優(yōu)秀!!!
其他相關文章
SpringMVC入門筆記
SpringMVC工作原理之處理映射[HandlerMapping]
SpringMVC工作原理之適配器[HandlerAdapter]
SpringMVC工作原理之參數解析
SpringMVC之自定義參數解析
SpringMVC工作原理之視圖解析及自定義
SpingMVC之<mvc:annotation-driven/>標簽