天天看點

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

SpringMVC是目前主流的Web MVC架構之一。

我們使用浏覽器通過位址 http://ip:port/contextPath/path進行通路,SpringMVC是如何得知使用者到底是通路哪個Controller中的方法,這期間到底發生了什麼。

本文将分析SpringMVC是如何處理請求與Controller之間的映射關系的,讓讀者知道這個過程中到底發生了什麼事情。

本文實際上是在上文基礎上,深入分析

HandlerMapping裡的      
HandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;      
該方法的具體實作,包括它如何找到對應的方法,以及如何把結果儲存在map裡,以便讓請求轉發到對應的handler上,同時也分析了handleradaptor具體做了什麼事情。      

源碼分析

在分析源碼之前,我們先了解一下幾個東西。

1.這個過程中重要的接口和類。

HandlerMethod類:

  Spring3.1版本之後引入的。 是一個封裝了方法參數、方法注解,方法傳回值等衆多元素的類。

  

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

  它的子類InvocableHandlerMethod有兩個重要的屬性WebDataBinderFactory和HandlerMethodArgumentResolverComposite, 很明顯是對請求進行處理的。

  InvocableHandlerMethod的子類ServletInvocableHandlerMethod有個重要的屬性HandlerMethodReturnValueHandlerComposite,很明顯是對響應進行處理的。

  ServletInvocableHandlerMethod這個類在HandlerAdapter對每個請求處理過程中,都會執行個體化一個出來(上面提到的屬性由HandlerAdapter進行設定),分别對請求和傳回進行處理。  (RequestMappingHandlerAdapter源碼,執行個體化ServletInvocableHandlerMethod的時候分别set了上面提到的重要屬性)

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

MethodParameter類:

  HandlerMethod類中的parameters屬性類型,是一個MethodParameter數組。MethodParameter是一個封裝了方法參數具體資訊的工具類,包括參數的的索引位置,類型,注解,參數名等資訊。

  HandlerMethod在執行個體化的時候,構造函數中會初始化這個數組,這時隻初始化了部分資料,在HandlerAdapter對請求處理過程中會完善其他屬性,之後交予合适的HandlerMethodArgumentResolver接口處理。

  以類DeptController為例:

@Controller
@RequestMapping(value = "/dept")
public class DeptController {

  @Autowired
  private IDeptService deptService;

  @RequestMapping("/update")
  @ResponseBody
  public String update(Dept dept) {
    deptService.saveOrUpdate(dept);
    return "success";
  }

}

           

  (剛初始化時的資料)  

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

  (HandlerAdapter處理後的資料)

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

RequestCondition接口:

  Spring3.1版本之後引入的。 是SpringMVC的映射基礎中的請求條件,可以進行combine, compareTo,getMatchingCondition操作。這個接口是映射比對的關鍵接口,其中getMatchingCondition方法關乎是否能找到合适的映射。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

RequestMappingInfo類:

  Spring3.1版本之後引入的。 是一個封裝了各種請求映射條件并實作了RequestCondition接口的類。

  有各種RequestCondition實作類屬性,patternsCondition,methodsCondition,paramsCondition,headersCondition,consumesCondition以及producesCondition,這個請求條件看屬性名也了解,分别代表http請求的路徑模式、方法、參數、頭部等資訊。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

RequestMappingHandlerMapping類:

  處理請求與HandlerMethod映射關系的一個類。

2.Web伺服器啟動的時候,SpringMVC到底做了什麼。

先看AbstractHandlerMethodMapping的initHandlerMethods方法中。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller
SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller
SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

我們進入createRequestMappingInfo方法看下是如何構造RequestMappingInfo對象的。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

PatternsRequestCondition構造函數:

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

類對應的RequestMappingInfo存在的話,跟方法對應的RequestMappingInfo進行combine操作。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

然後使用符合條件的method來注冊各種HandlerMethod。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

下面我們來看下各種RequestCondition接口的實作類的combine操作。

PatternsRequestCondition:

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller
SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

RequestMethodsRequestCondition:

方法的請求條件,用個set直接add即可。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

其他相關的RequestConditon實作類讀者可自行檢視源碼。

最終,RequestMappingHandlerMapping中兩個比較重要的屬性

private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();

private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();

T為RequestMappingInfo。

構造完成。

我們知道,SpringMVC的分發器DispatcherServlet會根據浏覽器的請求位址獲得HandlerExecutionChain。

這個過程我們看是如何實作的。

首先看HandlerMethod的獲得(直接看關鍵代碼了):

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

這裡的比較器是使用RequestMappingInfo的compareTo方法(RequestCondition接口定義的)。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

然後構造HandlerExecutionChain加上攔截器

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

執行個體

寫了這麼多,來點例子讓我們驗證一下吧。

@Controller
@RequestMapping(value = "/wildcard")
public class TestWildcardController {

  @RequestMapping("/test/**")
  @ResponseBody
  public String test1(ModelAndView view) {
    view.setViewName("/test/test");
    view.addObject("attr", "TestWildcardController -> /test/**");
    return view;
  }

  @RequestMapping("/test/*")
  @ResponseBody
  public String test2(ModelAndView view) {
    view.setViewName("/test/test");
    view.addObject("attr", "TestWildcardController -> /test*");
    return view;
  }

  @RequestMapping("test?")
  @ResponseBody
  public String test3(ModelAndView view) {
    view.setViewName("/test/test");
    view.addObject("attr", "TestWildcardController -> test?");
    return view;
  }

  @RequestMapping("test/*")
  @ResponseBody
  public String test4(ModelAndView view) {
    view.setViewName("/test/test");
    view.addObject("attr", "TestWildcardController -> test/*");
    return view;
  }

}

           

由于這裡的每個pattern都帶了*是以,都不會加入到urlMap中,但是handlerMethods還是有的。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

當我們通路:http://localhost:8888/SpringMVCDemo/wildcard/test1的時候。

會先根據 "/wildcard/test1" 找urlMap對應的RequestMappingInfo集合,找不到的話取handlerMethods集合中所有的key集合(也就是RequestMappingInfo集合)。

然後進行比對,比對根據RequestCondition的getMatchingCondition方法。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

最終比對到2個RequestMappingInfo:

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

然後會使用比較器進行排序。

之前也分析過,比較器是有優先級的。

我們看到,RequestMappingInfo除了pattern,其他屬性都是一樣的。

我們看下PatternsRequestCondition比較的邏輯:

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller
SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

是以,/test*的通配符比/test?的多,是以,最終選擇了/test?

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

直接比較優先于通配符。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller
@Controller
@RequestMapping(value = "/priority")
public class TestPriorityController {

  @RequestMapping(method = RequestMethod.GET)
  @ResponseBody
  public String test1(ModelAndView view) {
    view.setViewName("/test/test");
    view.addObject("attr", "其他condition相同,帶有method屬性的優先級高");
    return view;
  }

  @RequestMapping()
  @ResponseBody
  public String test2(ModelAndView view) {
    view.setViewName("/test/test");
    view.addObject("attr", "其他condition相同,不帶method屬性的優先級高");
    return view;
  }

}

           

這裡例子,其他requestCondition都一樣,隻有RequestMethodCondition不一樣。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

看出,方法多的優先級越多。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

至于其他的RequestCondition,大家自行檢視源碼吧。

資源檔案映射

以上分析均是基于Controller方法的映射(RequestMappingHandlerMapping)。

SpringMVC中還有靜态檔案的映射,SimpleUrlHandlerMapping。

DispatcherServlet找對應的HandlerExecutionChain的時候會周遊屬性handlerMappings,這個一個實作了HandlerMapping接口的集合。

由于我們在*-dispatcher.xml中加入了以下配置:

<mvc:resources location="/static/" mapping="/static/**"/>
           

Spring解析配置檔案會使用ResourcesBeanDefinitionParser進行解析的時候,會執行個體化出SimpleUrlHandlerMapping。

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller
SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

其中注冊的HandlerMethod為ResourceHttpRequestHandler。

通路位址:http://localhost:8888/SpringMVCDemo/static/js/jquery-1.11.0.js

SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller
SpringMVC源碼分析4:DispatcherServlet如何找到正确的Controller

位址比對到/static/**。

最終SimpleUrlHandlerMapping找到對應的Handler -> ResourceHttpRequestHandler。

ResourceHttpRequestHandler進行handleRequest的時候,直接輸出資源檔案的文本内容。

總結

大緻上整理了一下SpringMVC對請求的處理,包括其中比較關鍵的類和接口,希望對讀者有幫助。

讓自己對SpringMVC有了更深入的認識,也為之後分析資料綁定,攔截器、HandlerAdapter等打下基礎。