1. 程式人生 > 實用技巧 >23 Spring MVC 系列 - 處理器對映器 HandlerMapping

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介面中只有一個方法。

核心類結構如下圖所示:

大致上分為兩大類:AbstractHandlerMethodMappingAbstractUrlHandlerMapping,兩個都繼承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 的子類從上面截圖的類結構可以看出來,大致分為兩類:

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



2.1.1 BeanNameUrlHandlerMapping

首先來看其父類 AbstractDetectingUrlHandlerMapping 怎麼呼叫 registerHandler(String urlPath, Object handler) 又怎麼匹配到配置在容器中的 handler 並將其注入到 AbstractUrlHandlerMappingthis.handlerMap 中。

上面的業務實現中,提到判斷是否符合當前處理器對映器的判斷規則是個抽象方法protected abstract String[] determineUrlsForHandler(String beanName); 這裡由BeanNameUrlHandlerMapping類來實現。

從上面的原始碼分析我們可以得知,在 SpringMVC 容器中,且在注入了 BeanNameUrlHandlerMapping 對映器的時候,只要是以 "/" 開頭的 bean 的 name,都會作為該對映器匹配的 Handler 物件。


這種模式對應的是繼承AbstractController類來開發自己業務的場景。現在很少了,現在比較多的是對映方法,因為對映類的話,一個類裡只能處理一個請求。

2.1.2 SimpleUrlHandlerMapping

在接下來咱們看看該對映器是怎麼呼叫父類的 registerHandler(String urlPath, Object handler) 方法將 handler 加進 AbstractUrlHandlerMappingthis.handlerMap 中。

從上面原始碼可以看出 SimpleUrlHandlerMapping 對映器跟前面 BeanNameUrlHandlerMapping 對映器有點不一樣。後者是有點類似遍歷容器裡面有所的 beannameid 找到匹配的,並且 beannameid 有特殊要求,匹配的則加入。而前者則是先將加入該對映器的 handler 先加進該對映器的一個集合屬性裡面,容器初始化的時候免去了遍歷麻煩的步驟。

2.2 AbstractHandlerMapping 實現類分支之二 AbstractHandlerMethodMapping

AbstractHandlerMethodMapping最終獲取的handlerHandlerMethod型別物件。

具體實現類只有一個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 物件。

該實體類裡面最重要的兩個記錄集合分別是 mappingLookupurlLookup

  • urlLookup:主要用來記錄 lookupPath 請求路徑對應的 mapping 集合。
    這裡 Spring 留了一個很活的機制,拿 @RequestMapping 註解來說,他的 value 屬性本身就是一個字元陣列,在多重設定中難免有路徑重複的,所以最終有可能會出現一個 lookupPath 對應多個 RequestMappingInfo,最終在請求過來的時候給了自定義抽象方法讓實現類自己實現擇優的方式。
    MutivalueMap 是 SpringMVC 自定義的一個 Map 類,key 對應的 value 是一個集合,這從名字上也能看出來。
  • mappingLookup:key 是 mapping 物件,value 是 HandlerMethod 物件,最終是通過 lookupPathurlLookup 集合中找到對應的 mapping 物件,通過 mappingmappingLookup 集合中找到 HandlerMethod 物件。

③ 看下是怎麼將對映關係裝進快取(MappingRegistry) 物件中的
容器初始化的時候都幹了些什麼

isHandler(..)是該抽象類定義的抽象方法,由實現類自己去實現匹對哪些類。看下RequestMappingHandlerMapping對映器是怎麼實現的吧

看來RequestMappingHandlerMapping對映器,只要類上有ControllerRequestMapping註解,就符合該對映器管轄範圍。

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

猜也能猜到,RequestMappingHandlerMapping 對映器肯定匹配有 @RequestMapping 註解的方法,並返回該方法的對映資訊物件 RequestMappingInfo 物件。
下面就到了最後一步,具體這個對映關係是怎麼裝入對映器的MappingRegistry物件屬性的快取的呢?

三、 總結

到這裡,關於 SpringMVC 內部是怎麼通過 HandlerMapping 對映器將各自對應對映的資源在容器初始的時候裝到自身的快取,在請求過來時又是怎麼找到對應的資源返回最終對應的 handler 物件已經描述完了。
現在開發我們基本都不用 AbstractUrlHandlerMapping 這種型別的對映器了,但是 SpringMVC 內部還有用到的地方,例如直接 <mvc:view-controller path="" view-name=""/> 標籤配置資源不經過檢視控制器直接跳轉就用到了 SimpleUrlHandlerMapping 這種對映器。AbstractUrlHandlerMapping 匹對解析對應請求最終返回的 handlerController 物件。
現在我們習慣直接用 @Controller@RequestMapping 這樣註解來描述檢視控制器的邏輯,這種資源對映用的是 AbstractHandlerMethodMapping 抽象類的子類 RequestMappingHandlerMapping 對映器,匹對解析對應的請求返回HandlerMethod 物件。