面试官:你能告诉我一个请求过来,Spring MVC 是如何找到正确的 Controller 的?
前言
SpringMVC是目前主流的Web MVC框架之一。
我們使用瀏覽器通過地址 http://ip:port/contextPath/path進(jìn)行訪問,SpringMVC是如何得知用戶到底是訪問哪個(gè)Controller中的方法,這期間到底發(fā)生了什么。
本文將分析SpringMVC是如何處理請(qǐng)求與Controller之間的映射關(guān)系的,讓讀者知道這個(gè)過程中到底發(fā)生了什么事情。
源碼分析
在分析源碼之前,我們先了解一下幾個(gè)東西。
1.這個(gè)過程中重要的接口和類。
HandlerMethod類:
Spring3.1版本之后引入的。是一個(gè)封裝了方法參數(shù)、方法注解,方法返回值等眾多元素的類。
它的子類InvocableHandlerMethod有兩個(gè)重要的屬性WebDataBinderFactory和HandlerMethodArgumentResolverComposite, 很明顯是對(duì)請(qǐng)求進(jìn)行處理的。
InvocableHandlerMethod的子類ServletInvocableHandlerMethod有個(gè)重要的屬性HandlerMethodReturnValueHandlerComposite,很明顯是對(duì)響應(yīng)進(jìn)行處理的。
ServletInvocableHandlerMethod這個(gè)類在HandlerAdapter對(duì)每個(gè)請(qǐng)求處理過程中,都會(huì)實(shí)例化一個(gè)出來(上面提到的屬性由HandlerAdapter進(jìn)行設(shè)置),分別對(duì)請(qǐng)求和返回進(jìn)行處理。 (RequestMappingHandlerAdapter源碼,實(shí)例化ServletInvocableHandlerMethod的時(shí)候分別set了上面提到的重要屬性)
MethodParameter類:
HandlerMethod類中的parameters屬性類型,是一個(gè)MethodParameter數(shù)組。MethodParameter是一個(gè)封裝了方法參數(shù)具體信息的工具類,包括參數(shù)的的索引位置,類型,注解,參數(shù)名等信息。
HandlerMethod在實(shí)例化的時(shí)候,構(gòu)造函數(shù)中會(huì)初始化這個(gè)數(shù)組,這時(shí)只初始化了部分?jǐn)?shù)據(jù),在HandlerAdapter對(duì)請(qǐng)求處理過程中會(huì)完善其他屬性,之后交予合適的HandlerMethodArgumentResolver接口處理。
以類DeptController為例:
@Controller @RequestMapping(value?=?"/dept") public?class?DeptController?{@Autowiredprivate?IDeptService?deptService;@RequestMapping("/update")@ResponseBodypublic?String?update(Dept?dept)?{deptService.saveOrUpdate(dept);return?"success";}}(剛初始化時(shí)的數(shù)據(jù))
(HandlerAdapter處理后的數(shù)據(jù))
RequestCondition接口:
Spring3.1版本之后引入的。是SpringMVC的映射基礎(chǔ)中的請(qǐng)求條件,可以進(jìn)行combine, compareTo,getMatchingCondition操作。這個(gè)接口是映射匹配的關(guān)鍵接口,其中g(shù)etMatchingCondition方法關(guān)乎是否能找到合適的映射。
RequestMappingInfo類:
Spring3.1版本之后引入的。是一個(gè)封裝了各種請(qǐng)求映射條件并實(shí)現(xiàn)了RequestCondition接口的類。
有各種RequestCondition實(shí)現(xiàn)類屬性,patternsCondition,methodsCondition,paramsCondition,headersCondition,consumesCondition以及producesCondition,這個(gè)請(qǐng)求條件看屬性名也了解,分別代表http請(qǐng)求的路徑模式、方法、參數(shù)、頭部等信息。
RequestMappingHandlerMapping類:
處理請(qǐng)求與HandlerMethod映射關(guān)系的一個(gè)類。
2.Web服務(wù)器啟動(dòng)的時(shí)候,SpringMVC到底做了什么。
先看AbstractHandlerMethodMapping的initHandlerMethods方法中。
我們進(jìn)入createRequestMappingInfo方法看下是如何構(gòu)造RequestMappingInfo對(duì)象的。
PatternsRequestCondition構(gòu)造函數(shù):
類對(duì)應(yīng)的RequestMappingInfo存在的話,跟方法對(duì)應(yīng)的RequestMappingInfo進(jìn)行combine操作。
然后使用符合條件的method來注冊(cè)各種HandlerMethod。
下面我們來看下各種RequestCondition接口的實(shí)現(xiàn)類的combine操作。
PatternsRequestCondition:
RequestMethodsRequestCondition:
方法的請(qǐng)求條件,用個(gè)set直接add即可。
其他相關(guān)的RequestConditon實(shí)現(xiàn)類讀者可自行查看源碼。
最終,RequestMappingHandlerMapping中兩個(gè)比較重要的屬性
private final Map<T, HandlerMethod> handlerMethods = new LinkedHashMap<T, HandlerMethod>();
private final MultiValueMap<String, T> urlMap = new LinkedMultiValueMap<String, T>();
T為RequestMappingInfo。
構(gòu)造完成。
我們知道,SpringMVC的分發(fā)器DispatcherServlet會(huì)根據(jù)瀏覽器的請(qǐng)求地址獲得HandlerExecutionChain。
這個(gè)過程我們看是如何實(shí)現(xiàn)的。
首先看HandlerMethod的獲得(直接看關(guān)鍵代碼了):
這里的比較器是使用RequestMappingInfo的compareTo方法(RequestCondition接口定義的)。
然后構(gòu)造HandlerExecutionChain加上攔截器
實(shí)例
寫了這么多,來點(diǎn)例子讓我們驗(yàn)證一下吧。
@Controller @RequestMapping(value?=?"/wildcard") public?class?TestWildcardController?{@RequestMapping("/test/**")@ResponseBodypublic?String?test1(ModelAndView?view)?{view.setViewName("/test/test");view.addObject("attr",?"TestWildcardController?->?/test/**");return?view;}@RequestMapping("/test/*")@ResponseBodypublic?String?test2(ModelAndView?view)?{view.setViewName("/test/test");view.addObject("attr",?"TestWildcardController?->?/test*");return?view;}@RequestMapping("test?")@ResponseBodypublic?String?test3(ModelAndView?view)?{view.setViewName("/test/test");view.addObject("attr",?"TestWildcardController?->?test?");return?view;}@RequestMapping("test/*")@ResponseBodypublic?String?test4(ModelAndView?view)?{view.setViewName("/test/test");view.addObject("attr",?"TestWildcardController?->?test/*");return?view;}}由于這里的每個(gè)pattern都帶了*因此,都不會(huì)加入到urlMap中,但是handlerMethods還是有的。
當(dāng)我們?cè)L問:http://localhost:8888/SpringMVCDemo/wildcard/test1的時(shí)候。
會(huì)先根據(jù) "/wildcard/test1" 找urlMap對(duì)應(yīng)的RequestMappingInfo集合,找不到的話取handlerMethods集合中所有的key集合(也就是RequestMappingInfo集合)。
然后進(jìn)行匹配,匹配根據(jù)RequestCondition的getMatchingCondition方法。
最終匹配到2個(gè)RequestMappingInfo:
然后會(huì)使用比較器進(jìn)行排序。
之前也分析過,比較器是有優(yōu)先級(jí)的。
我們看到,RequestMappingInfo除了pattern,其他屬性都是一樣的。
我們看下PatternsRequestCondition比較的邏輯:
因此,/test*的通配符比/test?的多,因此,最終選擇了/test?
直接比較優(yōu)先于通配符。
@Controller @RequestMapping(value?=?"/priority") public?class?TestPriorityController?{@RequestMapping(method?=?RequestMethod.GET)@ResponseBodypublic?String?test1(ModelAndView?view)?{view.setViewName("/test/test");view.addObject("attr",?"其他condition相同,帶有method屬性的優(yōu)先級(jí)高");return?view;}@RequestMapping()@ResponseBodypublic?String?test2(ModelAndView?view)?{view.setViewName("/test/test");view.addObject("attr",?"其他condition相同,不帶method屬性的優(yōu)先級(jí)高");return?view;}}這里例子,其他requestCondition都一樣,只有RequestMethodCondition不一樣。
看出,方法多的優(yōu)先級(jí)越多。
至于其他的RequestCondition,大家自行查看源碼吧。
資源文件映射
以上分析均是基于Controller方法的映射(RequestMappingHandlerMapping)。
SpringMVC中還有靜態(tài)文件的映射,SimpleUrlHandlerMapping。
DispatcherServlet找對(duì)應(yīng)的HandlerExecutionChain的時(shí)候會(huì)遍歷屬性handlerMappings,這個(gè)一個(gè)實(shí)現(xiàn)了HandlerMapping接口的集合。
由于我們?cè)?-dispatcher.xml中加入了以下配置:
<mvc:resources?location="/static/"?mapping="/static/**"/>Spring解析配置文件會(huì)使用ResourcesBeanDefinitionParser進(jìn)行解析的時(shí)候,會(huì)實(shí)例化出SimpleUrlHandlerMapping。
其中注冊(cè)的HandlerMethod為ResourceHttpRequestHandler。
訪問地址:http://localhost:8888/SpringMVCDemo/static/js/jquery-1.11.0.js
地址匹配到/static/**。
最終SimpleUrlHandlerMapping找到對(duì)應(yīng)的Handler -> ResourceHttpRequestHandler。
ResourceHttpRequestHandler進(jìn)行handleRequest的時(shí)候,直接輸出資源文件的文本內(nèi)容。
總結(jié)
大致上整理了一下SpringMVC對(duì)請(qǐng)求的處理,包括其中比較關(guān)鍵的類和接口,希望對(duì)讀者有幫助。
讓自己對(duì)SpringMVC有了更深入的認(rèn)識(shí),也為之后分析數(shù)據(jù)綁定,攔截器、HandlerAdapter等打下基礎(chǔ)。
總結(jié)
以上是生活随笔為你收集整理的面试官:你能告诉我一个请求过来,Spring MVC 是如何找到正确的 Controller 的?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么 char 数组比 String
- 下一篇: Spring Validation 最佳