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); } }
輸入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/**
在HandlerExecutionChain67行獲取到我們訪問的Controller,詳細過程參照原始碼分析部分