6. SpringMVC
SpringMVC的實(shí)現(xiàn)原理是通過servlet攔截所有URL來達(dá)到控制的目的。SpringMVC是基于Servlet的實(shí)現(xiàn)。真正的邏輯實(shí)現(xiàn)是在DispatcherServlet中進(jìn)行的,DispatcherServlet繼承了httpServlet,httpServlet繼承了Servlet。httpServlet重寫service方法,根據(jù)請求方法調(diào)用doGet,doPost等。Servlet接到請求就調(diào)用service方法。
對于一個web應(yīng)用,其部署在web容器中,web容器提供其一個全局的上下文環(huán)境,這個上下文就是ServletContext,其為后面的spring IoC容器提供宿主環(huán)境;
在web.xml中會提供有contextLoaderListener。在web容器啟動時,會觸發(fā)容器初始化事件,此時contextLoaderListener會監(jiān)聽到這個事件,其contextInitialized方法會被調(diào)用,在這個方法中,spring會初始化一個啟動上下文,這個上下文被稱為根上下文,即WebApplicationContext,這是一個接口類,確切的說,其實(shí)際的實(shí)現(xiàn)類是XmlWebApplicationContext。這個就是spring的IoC容器,其對應(yīng)的Bean定義的配置由web.xml中的context-param標(biāo)簽指定。在這個IoC容器初始化完畢后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE為屬性Key,將其存儲到ServletContext中,便于獲??;
contextLoaderListener監(jiān)聽器初始化完畢后,開始初始化web.xml中配置的Servlet,這個servlet可以配置多個(一個web應(yīng)用可以有多個DispatcherServlet),以最常見的DispatcherServlet為例,這個servlet實(shí)際上是一個標(biāo)準(zhǔn)的前端控制器,用以轉(zhuǎn)發(fā)、匹配、處理每個servlet請求。DispatcherServlet上下文在初始化的時候會建立自己的IoC上下文,用以持有spring mvc相關(guān)的bean。在建立DispatcherServlet自己的IoC上下文時,會利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先從ServletContext中獲取之前的根上下文(即WebApplicationContext)作為自己上下文的parent上下文。有了這個parent上下文之后,再初始化自己持有的上下文。這個DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化處理器映射、視圖解析等。這個servlet自己持有的上下文默認(rèn)實(shí)現(xiàn)類也是xmlWebApplicationContext。初始化完畢后,spring以與servlet的名字相關(guān)(此處不是簡單的以servlet名為Key,而是通過一些轉(zhuǎn)換)的屬性為屬性Key,也將其存到ServletContext中,以便后續(xù)使用。這樣每個servlet就持有自己的上下文,即擁有自己獨(dú)立的bean空間,同時各個servlet共享相同的父bean,即根上下文(第2步中初始化的上下文)定義的那些bean。
6.1 DispatcherServlet的初始化
DispatcherServlet的初始化過程主要是:
通過將當(dāng)前的servlet類型實(shí)例轉(zhuǎn)換為BeanWrapper類型實(shí)例,以便使用Spring中提供的注入功能進(jìn)行對應(yīng)屬性的注入。
初始化WebApplicationContext,
DispatcherServlet中會持有一個WebApplicationContext對象的成員變量(DispatcherServlet中持有的Ioc容器中有Controller、Spring默認(rèn)的各種HandlerMapping、HandlerAdapter):
a. 初始化WebApplicationContext,并設(shè)置其parent ApplicationContext。
b. 調(diào)用WebApplicationContext的refresh方法進(jìn)行配置文件加載。
c. 調(diào)用DispatcherServlet重寫的OnRefresh方法,初始化各種:
initMultipartResolver:用于處理文件上傳的類,
使用applicationContext.getBean()方法獲取,并傳入DispatcherServlet的一個成員變量中initLocaleResolver:處理國際化問題,也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略(DefaultStrategy:在DispatcherServlet有靜態(tài)代碼塊初始化,從文件中讀?。?/p>
initThemeResolver:處理網(wǎng)站的主題資源,也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略
initHandlerMappings:當(dāng)客戶端發(fā)出Request時DispatcherServlet會將Request提交給HandlerMapping,然后HandlerMapping根據(jù)Web Application Context的配置來回傳給DispatcherServlet相應(yīng)的Controller。
可以為DispatcherServlet提供多個Handler Mapping供其使用。DispatcherServlet在選用HandlerMapping的過程中,將根據(jù)我們所指定的一系列HandlerMapping的優(yōu)先級進(jìn)行排序(@Order注解),然后優(yōu)先使用優(yōu)先級在前的HandlerMapping。如果當(dāng)前的HandlerMapping能夠返回可用的Handler,DispatcherServlet則使用當(dāng)前返回的Handler進(jìn)行Web請求的處理,而不再繼續(xù)詢問其他的HandlerMapping。否則,DispatcherServlet將繼續(xù)按照各個HandlerMapping的優(yōu)先級進(jìn)行詢問,直到獲取一個可用的Handler為止。也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略可以有多個HandlerMapping。initHandlerAdapters:作為總控制器的派遣器servlet通過處理器映射得到處理器后,會輪詢處理器適配器模塊,查找能夠處理當(dāng)前HTTP請求的處理器適配器的實(shí)現(xiàn),處理器適配器模塊根據(jù)處理器映射返回的處理器類型,例如簡單的控制器類型、注解控制器類型或者遠(yuǎn)程調(diào)用處理器類型,來選擇某一個適當(dāng)?shù)奶幚砥鬟m配器的實(shí)現(xiàn),從而適配當(dāng)前的HTTP請求。
也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略,可以有多個HandlerAdapter。initHandlerExceptionResolvers:異常處理。也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略,可以有多個HandlerExceptionResolver。
initRequestToViewNameTranslator:Controller處理器方法沒有返回一個View對象或邏輯視圖名稱,并且在該方法中沒有直接往response的輸出流里面寫數(shù)據(jù)的時候,Spring就會采用約定好的方式提供一個邏輯視圖名稱。也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略。
initViewResolvers:在SpringMVC中,當(dāng)Controller將請求處理結(jié)果放入到ModelAndView中以后, DispatcherServlet會根據(jù)ModelAndView選擇合適的視圖View進(jìn)行渲染。ViewResolver接口定義了resolverViewName方法,根據(jù)viewName創(chuàng)建合適類型的View實(shí)現(xiàn)??梢耘渲肑SP相關(guān)的viewResolver。
也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略,可以有多個ViewResolver。initFlashMapManager:SpringMVC Flash attributes提供了一個請求存儲屬性,可供其他請求使用。在使用重定向時候非常必要,例如Post/Redirect/Get模式。Flash attributes在重定向之前暫存(就像存在session中)以便重定向之后還能使用,并立即刪除。默認(rèn)開啟。也是使用applicationContext.getBean()方法獲取,如果拿不到則使用默認(rèn)的策略。
// DispatcherServlet
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
6.2 DispatcherServlet的執(zhí)行邏輯
請求->調(diào)用HttpServlet的doGet、doPost方法->DispatcherServlet的doService->DispatcherServlet的doDispatch
doDispatch中的邏輯:
檢查是否為multipart請求,如果是則轉(zhuǎn)換為MultipartHTTPServletRequest;
-
根據(jù)Request信息在HandlerMappings中尋找對應(yīng)的handler;
a. 變量HandlerMappings中的所有hanlder,可以根據(jù)url查找匹配相應(yīng)的handler;
????i. 匹配時會將用戶輸入的url去除多余的”/“,如”/test//a///b”變成”/test/a/b"
????ii. RequestMapping(“/test/a/b”)在匹配時也會在最后加上”/“,保證能更”test/a/b/“匹配上
b.將查找到的handler加入到攔截器鏈中;
c. HandlerMapping中包含對應(yīng)的controller和方法,啟動時會將controller中的方法注入RequestMappingHandlerMapping中;
????i. Spring啟動時配置好相應(yīng)的HandlerMapping
image.png
????ii. RequestMappingHandlerMapping中會注入使用@RequestMapping注解的url映射關(guān)系
image.png
d. handler是一個封裝了Controller和具體要調(diào)用到Controller中的某個method的類(HandlerMethod),包含url到Controller到method的映射關(guān)系; 根據(jù)當(dāng)前handler尋找對應(yīng)的HandlerAdapter;
a. 遍歷所有的HandlerAdapter;
b. 調(diào)用HandlerAdapter的supports方法判斷是否匹配,supports方法實(shí)際上就是判斷handler的類型(如SimpleControllerHandlerAdapter就是判斷handler是否為Controller類型);
c.HandlerAdapter主要做一些校驗(yàn)和準(zhǔn)備工作(處理方法參數(shù)、相關(guān)注解、數(shù)據(jù)綁定、消息轉(zhuǎn)換、返回值、調(diào)用視圖解析器等等,如加了@ResponseBody就不會返回視圖),并調(diào)用handler的反射執(zhí)行方法;處理lastModified緩存;
調(diào)用攔截器preHandler方法;
-
執(zhí)行handler(調(diào)用HandlerAdapter.handle方法,該方法把具體處理邏輯委托給handler)并返回視圖ModelAndView;
a. @Controller注解由這個處理:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
b. 最終用拿到Controller的bean,以及對應(yīng)要執(zhí)行的method,利用反射調(diào)用method;
c. ModelAndView,view為要跳轉(zhuǎn)的頁面,model為注入的信息;
image.png 調(diào)用攔截器的postHandler方法
處理頁面跳轉(zhuǎn)(redirect:xx, forward:xx),并將Model中的屬性設(shè)置到request中;
6.3 父子容器
DispatcherServlet中持有一個Ioc容器,其父容器為Spring的根Ioc容器,DispatcherServlet可以有多個,他們持有的多個Ioc容器均為同一個Spring跟Ioc容器。
使用這種父子容器的主要原因在于:
a. 解耦:
i. 在J2EE三層架構(gòu)中,在service層我們一般使用spring框架, 而在web層則有多種選擇,如spring mvc、struts等。因此,通常對于web層我們會使用單獨(dú)的配置文件。如果我們將父子容器都配置與同一個applicationContext.xml中,那么我們想要將spring mvc替換為structs,還需要修改applicationContext.xml,而分為兩種配置則無需修改applicationContext.xml。
ii. 事實(shí)上,如果你的項(xiàng)目確定了只使用spring和spring mvc的話,你甚至可以將service 、dao、web層的bean都放到spring-servlet.xml中進(jìn)行配置,并不是一定要將service、dao層的配置單獨(dú)放到applicationContext.xml中,然后使用ContextLoaderListener來加載。在這種情況下,就沒有了Root WebApplicationContext,只有Servlet WebApplicationContext。
iii. 如果在子容器中取不到bean,則會去父容器中取。


