SpringMVC源碼閱讀:定位Controller


1.前言

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

本文將通過源碼分析,弄清楚SpringMVC如何找到我們定義的Controller

2.源碼分析

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

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

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

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

943行,我們在這里獲取HandlerAdapter,獲取到各種argumentResolvers,用來解析參數,還能獲取到各種returnValueHandlers

對HandlerMapping ctrl+h查看類繼承關系,找到RequestMappingHandlerMapping

我們找到了核心類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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM