Spring MVC原始碼分析——初始化過程
1. 概述
對於Web開發者,MVC模型是大家再熟悉不過的了,SpringMVC中,滿足條件的請求進入到負責請求分發的DispatcherServlet,DispatcherServlet根據請求url到控制器的對映(HandlerMapping中儲存),HandlerMapping最終返回HandlerExecutionChain,其中包含了具體的處理物件handler(也即我們程式設計時寫的controller)以及一系列的攔截器interceptors,此時DispatcherServlet會根據返回的HandlerExecutionChain中的handler找到支援這一處理器型別的介面卡(handlerAdapter),在處理器介面卡中最終會去呼叫控制器的請求響應方法並返回結果檢視(ModelAndView),得到結果檢視後,通過render方法完成結果的顯示。
HanderMapping的繼承體系:
HandlerAdapter的繼承體系:
同樣的檢視解析器ViewResolver針對不同的輸出格式也有一系列的實現類,具體可自己看。
2. 實現分析
以我自己的一個web專案中spring mvc的配置為例:
- <context:component-scanbase-package="cn.ds.log"/>
- <beanid="defaultHandlerMapping"
- class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"
- <bean
- class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
- </bean>
- <bean
- class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
- </bean>
- <beanclass="org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter"
- </bean>
- <beanid="viewResolver"
- class="org.springframework.web.servlet.view.InternalResourceViewResolver">
- <propertyname="prefix">
- <value>/WEB-INF/jsp/</value>
- </property>
- <propertyname="suffix">
- <value>.jsp</value>
- </property>
- </bean>
這裡因為是採用全註解的方式,所以先通過context:component-scan配置讓spring自定掃描的包路徑,接著配置handlerMapping、handlerAdapter及ViewResolver,幾乎包含了SpringMVC的配置中需要涉及的所有元素。後面需要涉及具體的HanderMapping等的實現時,將以這裡配置中的實現為例進行分析,其它的大家“同理可解”。⊙﹏⊙b汗
2.1 Spring MVC初始化流程
DispatcherServlet的繼承體系如:
看到它們繼承自HttpServlet,你就知道初始化過程應該是從init方法開始了,整個初始化的流程為:
很簡單是麼?我也覺得是,至少從上面的時序圖看來是這樣,不過前提是你已經很瞭解Spring IOC的實現原理了。上面的時序圖中,在5的initStragegies()中除了呼叫6,7的函式外,還有幾個類似的初始化函式,因為這裡主要是為了理解整個的流程,所以我都省略了。上面流程可能需要分析的地方就在於3,4步,我們看看initWebApplicationContext函式的實現:
- protectedWebApplicationContext initWebApplicationContext(){
- WebApplicationContext rootContext =
- WebApplicationContextUtils.getWebApplicationContext(getServletContext());
- WebApplicationContext wac =null;
- if(this.webApplicationContext !=null){
- // A context instance was injected at construction time -> use it
- wac =this.webApplicationContext;
- if(wac instanceofConfigurableWebApplicationContext){
- ConfigurableWebApplicationContext cwac =(ConfigurableWebApplicationContext) wac;
- if(!cwac.isActive()){
- ……
- configureAndRefreshWebApplicationContext(cwac);
- }
- }
- }
- if(wac ==null){
- wac = findWebApplicationContext();
- }
- if(wac ==null){
- // No context instance is defined for this servlet -> create a local one
- wac = createWebApplicationContext(rootContext);
- }
- if(!this.refreshEventReceived){
- // Either the context is not a ConfigurableApplicationContext with refresh
- // support or the context injected at construction time had already been
- // refreshed -> trigger initial onRefresh manually here.
- onRefresh(wac);
- }
- if(this.publishContext){
- ……
- }
- return wac;
- }
看起來貌似有點複雜,其實理解了IOC容器的實現原理(可以看下“spring ioc原始碼分析”一文,⊙﹏⊙多年前弄的,這次暑假實習時又以讀書報告的形式寫了,感覺當年腫麼可以寫得這麼亂……也是一種成長,不打算修改)就很簡單,函式一開始會去獲取WebApplicationContext物件,這個物件在ContextLoaderListener初始化IOC容器時就已經把它set到ServletContext的屬性中,而且它也正是ConfigurableWebApplicationContext的例項,第一個if語句其實就是如果此時SpringIOC容器沒有初始化的話就在這裡啟動IOC容器的初始化過程,因為看“省略(1)”中的程式碼你就知道,它會在這裡呼叫refresh函式,“世人”都知道這就是IOC容器啟動的入口,這裡會解析配置檔案springmvc-servlet.xml。
這裡最終要執行onRefresh(),而這個就是SpringMVC初始化的入口。
(注:其實這裡也可以配置log4j,通過其列印的info資訊來看IOC與MVC的初始化順序)