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