SpringMVC源碼閱讀:定位Controller

1.前言

SpringMVC是目前J2EE平臺的主流Web框架,不熟悉的園友能夠看SpringMVC源碼閱讀入門,它交代了SpringMVC的基礎知識和源碼閱讀的技巧html

本文將經過源碼分析,弄清楚SpringMVC如何找到咱們定義的Controllerweb

2.源碼分析

org.springframework.web包有49017行代碼,咱們不可能去逐行閱讀,那麼,如何尋找源碼的蹤影?spring

順着上篇博文的思路回到DispatcherServlet類的doDispatch方法,咱們獲取到了HandlerExecutionChainjson

點開getHandler方法,發現HandlerExecutionChain是經過HandlerMapping獲取的。數組

DispatcherServlet分析參見SpringMVC源碼閱讀:核心分發器DispatcherServlet瀏覽器

943行,咱們在這裏獲取HandlerAdapter,獲取到各類argumentResolvers,用來解析參數,還能獲取到各類returnValueHandlersmvc

對HandlerMapping ctrl+h查看類繼承關係,找到RequestMappingHandlerMappingapp

咱們找到了核心類RequestMappingHandlerMapping,註釋說,該類用於建立@RequestMapping和@Controller註解的實例,在3.1版本加入,這正是咱們所須要的框架

根據官方文檔提示,咱們在調用RequestMappingHandlerMapping的時候真正調用的是HandlerMethod,打開HandlerMethod(注意是org.springframework.web包下)函數

HandlerMethod是3.1版本引入的,爲參數、返回值和註解提供便捷的封裝

打開HandlerMethod的子類InvocableHandlerMethod,發現有WebDataBinderFactory,HandlerMethodArgumentResolverComposite,ParameterNameDiscoverer,這三個屬性顯然是用來處理請求參數的

再打開InvocableHandlerMethod的子類ServletInvocableHandlerMethod,發現有HandlerMethodReturnValueHandlerComposite,這個屬性顯然是用來處理響應、返回值的

打開HandlerAdpter的子類RequestMappingHandlerAdapter,咱們看看它到底作了什麼

實例化ServletInvocableHandlerMethod,注入HandlerMethodArgumentResolverComposite,HandlerMethodReturnValueHandlerComposite和ParameterNameDiscoverer,在4.2版本之前,ServletInvocableHandlerMethod在createRequestMappingMethod方法中實例(已廢棄,該方法已刪除)

如今咱們看看HandlerMethod子類InvocableHandlerMethod到底作了什麼,瀏覽器輸入http://localhost:8080/springmvcdemo/employee/detail/1

    @RequestMapping(method = RequestMethod.GET, value = "/detail/{employeeId}",
            produces={"application/json; charset=UTF-8"})
    @ResponseBody
    public ModelAndView detail(@PathVariable Integer employeeId, ModelAndView view) {
        view.setViewName("employee/form");
        view.addObject("employee", employeeService.getById(employeeId));
        view.addObject("depts", deptService.listAll());
        return view;
    }

147行獲取參數詳細信息,如參數名稱,148行以後解析參數值Value

parameters在MethodParameter初始化完畢後的數據以下

咱們有Integer和ModelAndView類型的兩個參數,第一個參數指明瞭名稱叫作"employeeId",第二個未指明名稱,因此爲null。如今,咱們打開MethodParameter類,看看這些參數信息是從哪裏來的

在MethodParameter類裏,咱們看到了參數的原始形態,僅有parameterIndex和nestingLevel。parameterIndex=0表示第一個參數,等於2表示第二個參數,以此類推

這裏定義了拷貝構造函數,初始化參數的詳細信息,獲取每一個屬性的方法園友能夠自行打斷點查看,再也不逐個贅述。

再回到InvocableHandlerMethod類,回到148行,咱們看看參數值是如何獲取的,先看args是什麼

不難理解,第一個參數"employeeId"=1,第二個參數是null,繼續往下走

148行聲明Object數組,存儲參數值,151行初始化ParameterNameDiscovery,僅爲以後方法調用可使用discover,並未真正解析參數

158~159行,調用HandlerMethodArgumentResolverComposite解析並獲得參數值

回到RequestMappingHandlerMapping的父類AbstractHandlerMethodMapping,咱們看看initHandlerMethods方法作了什麼,啓動服務會進入該方法

197~199行初始化全部Bean並獲取beanName

205行獲取Bean類型

213行isHandler判斷bean類型是否是RequestMapping或者Controller,在子類RequestMappingHandlerMapping實現

214行尋找HandlerMethod,218行調用全部被偵測到的HandlerMethod

 點開detectHandlerMethods方法,看看它具體作了什麼

 

226~228利用反射依次獲取Controller,打斷點我發現handlerType和userType內容是一致的,getUserClass方法註釋說明是爲了獲取指定handlerType的類,我以爲這一步畫蛇添足

230行依次獲取Controller全部方法

235行getMappingForMethod方法由當前類(AbstractHandlerMethodMapping)的子類RequestMappingHandlerMapping的實現,返回RequestMappingInfo

248行遍歷methods,取出方法,249行泛型T是RequestMappingInfo

250行註冊HandlerMethod

打破砂鍋弄到底,繼續進入getMappingForMethod方法,ctrl+alt+b快速跳轉到子類實現

再進入createRequestMappingInfo方法

205行依次獲取Controller的方法上@RequestMapping註解的屬性

208行requestMapping不爲空則調用createRequestMappingInfo重載方法(兩個參數)用來構造RequestMappingInfo

208行點進去createRequestMappingInfo重載方法,一探究竟

繼續深刻,進入RequestMappingInfo,看這個類在作什麼,按ctrl+alt+u,看類的接口實現關係

看來,RequestMappingInfo實現了RequestCondition,在RequestMappingInfo類中咱們觀察combine方法,它把路徑、方法、參數、頭等信息進行了combine操做,combine是指將兩個或多個實例根據規則進行組合

打開RequestCondition子類PatternsRequestCondition,找到combine方法,在169行ctrl+alt+b跳轉至實現類AntPathMatcher combine方法,此方法初始化纔會進入

 

550行以"/*"結尾,截取再拼接,做者註釋很清楚,不贅述

556行以"/**"結尾,直接拼接,請看註釋

其餘的AbstractRequestCondition子類再也不介紹,園友能夠本身斷點調試看看

3.測試

 寫一個Controller

@Controller
@RequestMapping("wildcard")
public class TestWildcardController {

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

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

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

 

 輸入http://localhost:8080/springmvcdemo/wildcard/test/fff111,結果以下,進入@RequestMapping("/test/*")

輸入http://localhost:8080/springmvcdemo/wildcard/test/fff111/sss,結果以下,進入@RequestMapping("/test/**")

根據咱們剛纔源碼分析,輸入/test/fff111,AntPathMatcher combine方法幫咱們"/test/*".substring(0,6),而後和fff111 concat最終成爲/test/fff111

 

輸入/test/fff111/sss,就簡單了,直接concat,也就是說,test後面能夠加上任意個參數

在瀏覽器輸入http://localhost:8080/springmvcdemo/wildcard/test2,進入@RequestMapping("/test?")

test?,只能匹配一個,輸入test會直接進入/test/**

在HandlerExecutionChain 67行獲取到咱們訪問的Controller的HandlerMethod,詳細過程參照源碼分析部分

4.總結

MethodParameter封裝方法參數,HandlerMethod實例化的時候初始化MethodParameter數組,再交由合適的HandlerMethodArgumentResolver(HandlerMethodArgumentResolverComposite的父類)處理

RequestCondition處理請求映射關係,使用combine方法合併不一樣的條件,getMatchingConditon方法獲取映射,compareTo方法爲映射排序

RequestMappingInfo實現RequestCondition,集中將各類RequestCondition進行combine,getMatchingConditon,compareTo

RequestMappingHandlerMapping處理請求與HandlerMethod映射關係,找出@RequestMapping和@Controller修飾的類和方法

5.參考

https://docs.spring.io/spring/docs/4.3.7.RELEASE/spring-framework-reference/htmlsingle/#mvc-servlet

相關文章
相關標籤/搜索