23 Spring MVC 系列 - 處理器對映器 HandlerMapping
原文連線
到目前為止,已經知道在Spring MVC的架構環境下,使用者在web端觸發了請求之後,請求會先通過前端控制器 DispatcherServlet,然後DispatcherServlet 會請求處理器對映器 HandlerMapping 尋找處理該請求的 Handler(或帶攔截器的Handler鏈),接著DispatcherServlet 會根據找到的 Handler 與配置的處理器介面卡 HandlerAdapter...先到此打住。
也就是說對於使用者請求,處理器對映器 HandlerMapping 為前端控制器 DispatcherServlet 和處理器 Handler的互動搭建了橋樑。
HandlerMapping只是一個介面類,不同的實現類有不同的匹對方式,根據功能的不同我們需要在SpringMVC容器中注入不同的對映器處理器HandlerMapping。
簡單工作如下:
一、HandlerMapping
介面
1.1 HandlerMapping
注入
在DispatcherServlet
類中有下面這個方法
public class DispatcherServlet extends FrameworkServlet {
private void initHandlerMappings(ApplicationContext context) {...}
}
容器被初始化的時候該方法會被呼叫,載入容器中注入的HandlerMapping
我們可以在springmvc.xml中設定處理器對映器,現在更多使用Springboot開發時不設定springmvc.xml配置檔案時,就會讀取spring-webmvcjar包中的預設的配置檔案
DispatcherServlet.properties
,可以看到這裡配置的HandlerMapping是RequestMappingHandlerMapping。
1.2HandlerExecutionChain
初始化
在DispatcherServlet
類中,doDispatch(..)
方法總通過呼叫本類的getHandler(..)
方法得到HandlerExecutionChain
物件。
也就是說,這裡也就映射了開頭所說的前端處理器DispatcherServlet
HandlerMapping
獲取HandlerExecutionChain
物件,是通過遍歷HandlerMapping
的集合物件handlerMappings
來獲取的。1.3 HandlerMapping
介面
在HandlerMapping
介面中只有一個方法。
核心類結構如下圖所示:
大致上分為兩大類:AbstractHandlerMethodMapping
和AbstractUrlHandlerMapping
,兩個都繼承AbstractHandlerMapping
實現HandlerMapping
介面。
二、HandlerMapping
介面實現抽象類 AbstractHandlerMapping
在AbstractHandlerMapping類中實現getHandler(...)介面方法得到HandlerExecutionChain物件。AbstractHandlerMapping
實現HandlerMapping
介面,同時也繼承WebApplicationObjectSupport
,即服務啟動時會自動呼叫模版方法
2.1 AbstractHandlerMapping
實現類分支之一 AbstractUrlHandlerMapping
AbstractUrlHandlerMapping
:URL 對映的抽象基類,提供將處理程式對映到Controller
,所以該類最終直接返回的handler
就是Controller
物件。
實現父抽象類的抽象方法getHandlerInternal(..)
匹配並返回對應的Handler
物件。
接下來咱們看看根據路徑匹對handler
的方法lookupHandler(..)
從程式碼中可以看出,這裡所謂的查詢Handler
就是從this.handlerMap
里根據urlPath
查詢,那問題來了,是什麼時候怎麼樣將Handler
放到this.handlerMap
裡的呢?
比較開心的是this.handlerMap
這個map物件在當前類中就一個地方進行了put操作。在protected void registerHandler(String urlPath, Object handler) {}
這個方法中進行了put操作。
從原始碼看是在AbstractUrlHandlerMapping
子類裡面呼叫該registerHandler
方法。AbstractUrlHandlerMapping
的子類從上面截圖的類結構可以看出來,大致分為兩類:
- 間接繼承
AbstractUrlHandlerMapping
的BeanNameUrlHandlerMapping
- 直接繼承
AbstractUrlHandlerMapping
的SimpleUrlHandlerMapping
2.1.1 BeanNameUrlHandlerMapping
首先來看其父類 AbstractDetectingUrlHandlerMapping
怎麼呼叫 registerHandler(String urlPath, Object handler)
又怎麼匹配到配置在容器中的 handler
並將其注入到 AbstractUrlHandlerMapping
的 this.handlerMap
中。
上面的業務實現中,提到判斷是否符合當前處理器對映器的判斷規則是個抽象方法protected abstract String[] determineUrlsForHandler(String beanName);
這裡由BeanNameUrlHandlerMapping
類來實現。
從上面的原始碼分析我們可以得知,在 SpringMVC 容器中,且在注入了 BeanNameUrlHandlerMapping
對映器的時候,只要是以 "/" 開頭的 bean 的 name,都會作為該對映器匹配的 Handler
物件。
這種模式對應的是繼承AbstractController類來開發自己業務的場景。現在很少了,現在比較多的是對映方法,因為對映類的話,一個類裡只能處理一個請求。
2.1.2 SimpleUrlHandlerMapping
在接下來咱們看看該對映器是怎麼呼叫父類的 registerHandler(String urlPath, Object handler)
方法將 handler
加進 AbstractUrlHandlerMapping
的 this.handlerMap
中。
從上面原始碼可以看出 SimpleUrlHandlerMapping
對映器跟前面 BeanNameUrlHandlerMapping
對映器有點不一樣。後者是有點類似遍歷容器裡面有所的 bean
的 name
或 id
找到匹配的,並且 bean
的 name
或 id
有特殊要求,匹配的則加入。而前者則是先將加入該對映器的 handler
先加進該對映器的一個集合屬性裡面,容器初始化的時候免去了遍歷麻煩的步驟。
2.2 AbstractHandlerMapping
實現類分支之二 AbstractHandlerMethodMapping
AbstractHandlerMethodMapping
最終獲取的handler
是HandlerMethod
型別物件。
具體實現類只有一個RequestMappingHandlerMapping,這中模式也就是我們現在比較常用的使用@Controller
和@RequestMapping
這樣註解來描述檢視控制器的邏輯。
2.2.1 簡單瞭解 HandlerMethod
HandlerMethod
其實可以簡單理解為保持方法資訊的pojo類
2.2.2 RequestMappingInfo
類
主要用來記錄方法上@RequestMapping()
註解裡面的引數,針對RequestMappingHandlerMapping
對映器來使用。
在容器初始化過程中建立對映器(RequestMappingHandlerMapping
)物件時,會尋找所有被@Controller
註解類中被 @RequestMapping
註解的方法,然後解析方法上的 @RequestMapping
註解,把解析結果封裝成 RequestMappingInfo
物件,也就是說RequestMappingInfo
物件是用來裝載方法的匹配相關資訊,每個匹配的方法都會對應一個 RequestMappingInfo
物件。現在大家應該能明白 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,然後一個個匹對,非常耗時和費資源。優化請參考 springMVC在restful風格的效能優化
- 上面的兩個抽象方法(
getMatchingMapping(..)
和getMappingComparator(..)
)
前者要實現檢查提供的請求對映資訊中的條件是否與請求匹配。
後者要實現當一個Request
對應多個mapping
時的擇優方案。
② 看下儲存對映關係物件(MappingRegistry
)內部結構
說到這裡,可能大家對於這個 this.mappingRegistery
物件十分好奇,裡面到底是怎麼儲存資料的,先是可以根據 lookupPath
找到 List<mapping>
,接著後來又根據 mapping
找到 HandlerMethod
物件。
該實體類裡面最重要的兩個記錄集合分別是 mappingLookup
和 urlLookup
。
urlLookup
:主要用來記錄lookupPath
請求路徑對應的mapping
集合。
這裡 Spring 留了一個很活的機制,拿@RequestMapping
註解來說,他的value
屬性本身就是一個字元陣列,在多重設定中難免有路徑重複的,所以最終有可能會出現一個lookupPath
對應多個RequestMappingInfo
,最終在請求過來的時候給了自定義抽象方法讓實現類自己實現擇優的方式。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
物件屬性的快取的呢?
三、 總結
到這裡,關於 SpringMVC 內部是怎麼通過 HandlerMapping
對映器將各自對應對映的資源在容器初始的時候裝到自身的快取,在請求過來時又是怎麼找到對應的資源返回最終對應的 handler
物件已經描述完了。
現在開發我們基本都不用 AbstractUrlHandlerMapping
這種型別的對映器了,但是 SpringMVC 內部還有用到的地方,例如直接 <mvc:view-controller path="" view-name=""/>
標籤配置資源不經過檢視控制器直接跳轉就用到了 SimpleUrlHandlerMapping
這種對映器。AbstractUrlHandlerMapping
匹對解析對應請求最終返回的 handler
是 Controller
物件。
現在我們習慣直接用 @Controller
和 @RequestMapping
這樣註解來描述檢視控制器的邏輯,這種資源對映用的是 AbstractHandlerMethodMapping
抽象類的子類 RequestMappingHandlerMapping
對映器,匹對解析對應的請求返回HandlerMethod
物件。