1议纯、回顧servlet 與jsp 執(zhí)行過程
2苏潜、Spring MVC請求處理流程
3步责,mvc 體系結(jié)構(gòu)詳解
URL映射
表單參數(shù)映射
調(diào)用目標(biāo)Control
數(shù)據(jù)模型映射
視圖解析
異常處理
DispatcherServlet
DispatcherServlet它的作用有點兒類似于網(wǎng)關(guān)挺尾,DispatcherServlet負(fù)責(zé)將請求轉(zhuǎn)發(fā)到不同的Controller上去鹅搪。
配置DispatcherServlet
/ 后面不能寫成/*否則容易出問題。
編寫Controller遭铺,繼承controller類丽柿。實現(xiàn)handlerRequest接口。指定ModelAndView魂挂。這是一種方式甫题。
第二種方式 繼承 HttpRequestHandler類。那么DispatcherServlet是如何去匹配不同的controller的呢涂召?這就需要了解SpringMVC的體系結(jié)構(gòu)坠非。整個體系結(jié)構(gòu)都搞懂才行。下面就從一個全局的視角去看Spring MVC的體系結(jié)構(gòu)果正。
SpringMVC的體系結(jié)構(gòu)圖
DispatcherServlet是通過HandlerMapping的一組映射關(guān)系去找到我們的目標(biāo)Controller
通過上面我們知道Controller存在的形式是多種多樣的炎码。可以通過Controller接口的形式秋泳,也可以通過@Controller注解的形式存在潦闲。等等等。迫皱。歉闰。有這么多不同的Controller形式DispatcherServlet是如何去執(zhí)行的呢?這里面就涉及到一個HandlerAdapter控制器適配器舍杜。找到數(shù)據(jù)模型的一個映射新娜。然后試圖解析是通過ViewResolver這個東東。他支持各種各樣的試圖解析既绩。view用于具體的試圖解析概龄。HandlerExceptionResolver異常攔截解析器。
下面就圍繞這張圖去逐一進行源碼分析饲握。
一私杜,HandlerMapping源碼分析
HandlerMapping是一個接口蚕键,他有兩個實現(xiàn)類,MatchableHandlerMapping和AbstractHandlerMapping衰粹。HandlerMapping在getHandler里面并沒有返回handler(我們的目標(biāo)執(zhí)行器锣光,也就是我們的controller)而是返回一個執(zhí)行鏈條。HandlerExecutionChain铝耻。
根據(jù)下圖可知HandlerExecutionChain里面有一個getHandler方法誊爹,這里面返回了Handler。但是具體的是哪個一個Handler是不確定的瓢捉,因為他是Object類型的频丘。所以通過動態(tài)代理的方式是不能實現(xiàn)的。只能通過這個執(zhí)行鏈條去返回目標(biāo)Handler泡态。后續(xù)的文章我們會對執(zhí)行鏈條做深入的講解搂漠。
然后看上面的類繼承關(guān)系圖。左面繼承了AbstractUrlHandlerMapping其中AbstractDetectingUrlHandlerMapping起到自動發(fā)現(xiàn)的作用某弦,他是根據(jù)Bean的名稱桐汤,而且是必須以/開頭。比如這樣
<bean name="/hello.do" class="com.tuling.control.SimpleControl"/>
然后SimpleUrlHandlerMapping靶壮。如果不需要自動發(fā)現(xiàn)功能怔毛,則使用這個。SimpleUrlHandlerMapping是自己配置URL和controller之間的一個關(guān)系亮钦。所以他們之間的區(qū)別就是一個自動發(fā)現(xiàn)馆截,一個手動配置。AbstractHandlerMethodMapping是對方法進行映射RequestMappingInfoHandlerMapping這個大家比較熟悉了蜂莉,就是通過@RequestMapping進行映射的蜡娶。如果配置了SimpleUrlHandlerMapping或者BeanNameUrlHandlerMapping那么默認(rèn)的就會失效。SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping可以同時配置映穗,都可以映射到我們的Controller控制器窖张。
SimpleUrlHandlerMapping的初始化執(zhí)行流程
對initApplicationContext()方法進行調(diào)用。其中super.initApplicationContext();這里是初始化一些攔截器蚁滋。比如自動發(fā)現(xiàn)攔截器啊等等等宿接。this.registerHandlers(this.urlMap);開始進行Url的匹配點進去看一下可知如果urlMap不為空才進行匹配。第一步是!url.startsWith("/")如果不是/開頭的辕录。就會把你的URL加上/然后獲取Handler睦霎,就是我們的控制器。然后判斷handler是不是String類型的走诞,副女。如果是進行去除空格的處理。然后執(zhí)行this.registerHandler(url, handler);點進去發(fā)現(xiàn)蚣旱,將handler轉(zhuǎn)化為Object類型碑幅。然后判斷是否是懶加載并且是String類型戴陡。然后將Handler轉(zhuǎn)化成String的字符串。然后獲取一個ApplicationContext對象沟涨。判斷handler是都是單例恤批。如果是通過ApplicationContext.getBean(handlerName)獲取到一個Object對象。然后獲取handlerMap裹赴,判斷是否是空喜庞,如果不為空說明你配置了兩個name屬性此時會拋出異常。接著判斷你的url是否等于/如果是設(shè)置一個根目錄的Handler通過localhost:8080/即可訪問我們的handler篮昧。然后判斷是否等于/*如果是就設(shè)置一個默認(rèn)的和Handler赋荆。也就是說你永遠(yuǎn)都不會擔(dān)心找不到這個url的異常笋妥。
最后如果以上情況都不是懊昨,則通過this.handlerMap.put(urlPath, resolvedHandler);將url和handler綁定在一起。如果說handler是懶加載那么此時map中存儲的value就是beanName春宣。這就是SimpleUrlHandlerMapping的初始化流程酵颁。
SimpleUrlHandlerMapping的訪問執(zhí)行流程
我們知道最終執(zhí)行調(diào)用的一定是DispatcherServlet。所以我們從這里開始入手月帝。找到getHandler
首先通過Iterator var2 =this.handlerMappings.iterator();或取到所有的Mapping然后遍歷躏惋。遍歷一次取出一個handlerMapping然后調(diào)用handlerMapping。點進去看看嚷辅。第一行g(shù)etHandlerInternal這個方法很重要簿姨。點進去看看,在這個方法里第一行通過getUrlPathHelper().getLookupPathForRequest(request);去解析我們地址欄上的URL簸搞。比如或取到的是/hello.do扁位。第二行通過Object handler = lookupHandler(lookupPath, request);去查找對應(yīng)的handler。點進去發(fā)現(xiàn)就是從上面put進去的handlerMap找到對應(yīng)的handler趁俊。在最后return一個buildPathExposingHandler域仇。點進去發(fā)現(xiàn)HandlerExecutionChain這里面配置了一個攔截器鏈,那么返回的并不是我們最終的handler寺擂。
那么getHandlerInternal方法的第二行也執(zhí)行完了暇务。最終拿到得是一個攔截器鏈,如果說攔截器鏈為空則判斷第一行的url解析器拿到得/hello.do是否等于/如果是Object rawHandler=getRootHandler()設(shè)置為根節(jié)點的handler怔软,然后判斷rawHandler是否等于空垦细,如果是則調(diào)用默認(rèn)的handler。如果不等于空呢挡逼,則直接通過beanName從Spring容器找到Handler括改。 再調(diào)用buildPathExposingHandler。還是獲取一個攔截器鏈挚瘟。最終叹谁,將攔截器鏈返回饲梭。getHandlerInternal方法執(zhí)行結(jié)束。返回到getHandler方法里焰檩。那么如果說憔涉,沒有獲取到攔截器鏈,則獲取默認(rèn)的handler析苫。如果不為空兜叨,則直接通過beanName從容器中獲取到handler。然后衩侥,去創(chuàng)建一個攔截器鏈條国旷,又加入了一個攔截器。最后進一步獲取handler茫死。然后返回HandlerExecutionChain跪但。最后通過HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());獲取到的才是我們的handler。SimpleUrlHandlerMapping執(zhí)行流程到此介紹完畢峦萎,說實話SpringMVC的代碼寫的跟IOC\aop差著一大塊兒的水平感覺屡久。
Controller 接口:
HttpRequestHandler 接口:
HttpServlet 接口:
@RequestMapping方法注解
可以看出 Handler 沒有統(tǒng)一的接口,當(dāng)dispatchServlet獲取當(dāng)對應(yīng)的Handler之后如何調(diào)用呢爱榔?調(diào)用其哪個方法被环?這里有兩種解決辦法,一是用instanceof 判斷Handler 類型然后調(diào)用相關(guān)方法 详幽。二是通過引入適配器實現(xiàn)筛欢,每個適配器實現(xiàn)對指定Handler的調(diào)用。spring 采用后者唇聘。
HandlerAdapter詳解
HandlerAdapter中有三個接口
1supports接口版姑,主要的作用是傳入一個handler看看是否可以被執(zhí)行。如果可以返回true雳灾。
2handle處理接口漠酿,處理完返回一個ModelAndView。
3getLastModified谎亩,用作緩存處理炒嘲,獲取最后一次修改時間。
我們知道HandlerAdapter對應(yīng)適配了4種handler關(guān)系圖如下匈庭。
舉個例子夫凸,如果說我們寫了一個Servlet,然后繼承HttpServlet此時如果不配置SimpleServletHandlerAdapter適配器阱持,那么就會報錯夭拌,報500的異常。但是如果配置了這個適配器,那么SpringMVC給我們配置的默認(rèn)適配器SimpleControllerHandlerAdapter就會失效鸽扁。所以需要手動的配置一下蒜绽。下面看一下源碼即可驗證這個說法。上面講到返回一個攔截器鏈條桶现。然后下面的代碼就是獲取適配器如下圖躲雅。
點進getHandlerAdapter方法。這個方法里做了一個do-while循環(huán)骡和。通過support方法判斷是否是可用的適配器相赁。
在下圖的方法里判斷是否是我們配置的適配器,如果是則返回true慰于。
拿到適配器以后就開始了真正的調(diào)用钮科。
此時適配器找到了以后就開始真正的調(diào)用handler也就是我們servlet或者是controller。然后通過一個叫ViewResolver的接口類去解析婆赠。其中有一個接口叫resolveViewName绵脯。他返回一個View。然后View里面有一個render接口页藻。通過里面的model進行處理桨嫁,封裝HTML。通過response進行寫入份帐。所以它是先有ViewResolver才有的View。
然后看看View有哪些實現(xiàn)類如下圖楣导。
第一個AbstractTemplateViewResolver里面有以下兩種視圖支持废境,比如我們常用的FreeMarkerViewReslover。
第二個BeanNameViewResolver可以通過這種方式實現(xiàn)自定的視圖解析器筒繁,生明一個自定義View類噩凹。然后實現(xiàn)View接口。在render里面去做返回毡咏。然后controller里面通過ModelAndView視圖解析器的構(gòu)造器去加載我們的自定義View類型驮宴。
第三個就是InternalResourceViewResolver。這個就是我們最常用的呕缭,通過配置前綴堵泽,后綴等信息去實現(xiàn)視圖解析。
下圖是通過BeanName的形式做的視圖解析
最后附上一張整體的流程圖