在上一篇文章中,咱們給出了構成SpringMVC應用程序的三要素以及三要素的設計過程。讓咱們來概括一下整個設計過程當中的一些要點: java
- SpringMVC將Http處理流程抽象爲一個又一個處理單元
- SpringMVC定義了一系列組件(接口)與全部的處理單元對應起來
- SpringMVC由DispatcherServlet貫穿始終,並將全部的組件串聯起來
在整個過程當中,組件和DispatcherServlet老是維持着一個相互支撐的關係: 程序員
- DispatcherServlet —— 串聯起整個邏輯主線,是整個框架的心臟
- 組件 —— 邏輯處理單元的程序化表示,起到承上啓下的做用,是SpringMVC行爲模式的實際承載者
在本系列接下來的兩篇文章中,咱們將分別討論DispatcherServlet和組件的相關內容。本文討論DispatcherServlet,而下一篇則重點分析組件。
有關DispatcherServlet,咱們想從構成DispatcherServlet的體系結構入手,再根據不一樣的邏輯主線分別加以分析,但願可以幫助讀者整理出學習SpringMVC核心類的思路。
DispatcherServlet的體系結構
經過不一樣的角度來觀察DispatcherServlet會獲得不一樣的結論。咱們在這裏選取了三個不一樣的角度:運行特性、繼承結構和數據結構。
【運行主線】
從DispatcherServlet所實現的接口來看,DispatcherServlet的核心本質:是一個Servlet。這個結論彷佛很幼稚,不過這個幼稚的結論卻蘊含了一個對整個框架都相當重要的內在原則:Servlet能夠根據其特性進行運行主線的劃分。
根據Servlet規範的定義,Servlet中的兩大核心方法init方法和service方法,它們的運行時間和觸發條件都大相徑庭:
1. init方法
在整個系統啓動時運行,且只運行一次。所以,在init方法中咱們每每會對整個應用程序進行初始化操做。這些初始化操做可能包括對容器(WebApplicationContext)的初始化、組件和外部資源的初始化等等。
2. service方法
在整個系統運行的過程當中處於偵聽模式,偵聽並處理全部的Web請求。所以,在service及其相關方法中,咱們看到的則是對Http請求的處理流程。
於是在這裏,Servlet的這一特性就被SpringMVC用於對不一樣的邏輯職責加以劃分,從而造成兩條互不相關的邏輯運行主線: web
- 初始化主線 —— 負責對SpringMVC的運行要素進行初始化
- Http請求處理主線 —— 負責對SpringMVC中的組件進行邏輯調度完成對Http請求的處理
對於一個MVC框架而言,運行主線的劃分很是重要。由於只有弄清楚不一樣的運行主線,咱們才能針對不一樣的運行主線採起不一樣的研究策略。而咱們在這個系列中的絕大多數分析的切入點,也是圍繞着不一樣的運行主線進行的。
注:SpringMVC運行主線的劃分依據是Servlet對象中不一樣方法的生命週期。事實上,幾乎全部的MVC都是以此爲依據來進行運行主線的劃分。這進一步能夠證實全部的MVC框架的核心基礎仍是Servlet規範,而設計理念的差別也致使了不一樣的框架走向了徹底不一樣的發展道路。
【繼承結構】
除了運行主線的劃分之外,咱們再關注一下DispatcherServlet的繼承結構:
在這個繼承結構中,咱們能夠看到DispatcherServlet在其繼承樹中包含了2個Spring的支持類:HttpServletBean和FrameworkServlet。咱們分別來討論一下這兩個Spring的支持類在這裏所起到的做用。
HttpServletBean是Spring對於Servlet最低層次的抽象。在這一層抽象中,Spring會將這個Servlet視做是一個Spring的bean,並將init-param中的值做爲bean的屬性注入進來: spring
public final void init() throws ServletException {
2. if (logger.isDebugEnabled()) {
3. logger.debug("Initializing servlet '" + getServletName() + "'");
4. }
5.
6. // Set bean properties from init parameters.
7. try {
8. PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
9. BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
10. ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
11. bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment));
12. initBeanWrapper(bw);
13. bw.setPropertyValues(pvs, true);
14. }
15. catch (BeansException ex) {
16. logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
17. throw ex;
18. }
19.
20. // Let subclasses do whatever initialization they like.
21. initServletBean();
22.
23. if (logger.isDebugEnabled()) {
24. logger.debug("Servlet '" + getServletName() + "' configured successfully");
25. }
26.}
從源碼中,咱們能夠看到HttpServletBean利用了Servlet的init方法的執行特性,將一個普通的Servlet與Spring的容器聯繫在了一塊兒。在這其中起到核心做用的代碼是:BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);將當前的這個Servlet類轉化爲一個BeanWrapper,從而可以以Spring的方式來對init-param的值進行注入。BeanWrapper的相關知識屬於Spring Framework的內容,咱們在這裏不作詳細展開,讀者能夠具體參考HttpServletBean的註釋得到更多的信息。
FrameworkServlet則是在HttpServletBean的基礎之上的進一步抽象。經過FrameworkServlet真正初始化了一個Spring的容器(WebApplicationContext),並引入到Servlet對象之中: 編程
protected final void initServletBean() throws ServletException {
2. getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
3. if (this.logger.isInfoEnabled()) {
4. this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
5. }
6. long startTime = System.currentTimeMillis();
7.
8. try {
9. this.webApplicationContext = initWebApplicationContext();
10. initFrameworkServlet();
11. } catch (ServletException ex) {
12. this.logger.error("Context initialization failed", ex);
13. throw ex;
14. } catch (RuntimeException ex) {
15. this.logger.error("Context initialization failed", ex);
16. throw ex;
17. }
18.
19. if (this.logger.isInfoEnabled()) {
20. long elapsedTime = System.currentTimeMillis() - startTime;
21. this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
22. elapsedTime + " ms");
23. }
24.}
上面的這段代碼就是FrameworkServlet初始化的核心代碼。從中咱們能夠看到這個FrameworkServlet將調用其內部的方法initWebApplicationContext()對Spring的容器(WebApplicationContext)進行初始化。同時,FrameworkServlet還暴露了與之通信的結構可供子類調用: 緩存
public abstract class FrameworkServlet extends HttpServletBean {
2.
3. /** WebApplicationContext for this servlet */
4. private WebApplicationContext webApplicationContext;
5.
6. // 這裏省略了其餘全部的代碼
7.
8. /**
9. * Return this servlet's WebApplicationContext.
10. */
11. public final WebApplicationContext getWebApplicationContext() {
12. return this.webApplicationContext;
13. }
14.}
咱們在這裏暫且不對Spring容器(WebApplicationContext)的初始化過程詳加探查,稍後咱們會討論一些WebApplicationContext初始化過程當中的配置選項。不過讀者能夠在這裏體會到:FrameworkServlet在其內部初始化了一個Spring的容器(WebApplicationContext)並暴露了相關的操做接口,於是繼承自FrameworkServlet的DispatcherServlet,也就直接擁有了與WebApplicationContext進行通訊的能力。
經過對DispatcherServlet繼承結構的研究,咱們能夠明確: 安全
downpour 寫道
結論 DispatcherServlet的繼承體系架起了DispatcherServlet與Spring容器進行溝通的橋樑。
【數據結構】
在上一篇文章中,咱們曾經提到過DispatcherServlet的數據結構:
咱們能夠把在上面這張圖中所構成DispatcherServlet的數據結構主要分爲兩類(咱們在這裏用一根分割線將其分割開來): 數據結構
- 配置參數 —— 控制SpringMVC組件的初始化行爲方式
- 核心組件 —— SpringMVC的核心邏輯處理組件
能夠看到,這兩類數據結構都與SpringMVC中的核心要素組件有關。所以,咱們能夠得出這樣一個結論: mvc
downpour 寫道
結論 組件是整個DispatcherServlet的靈魂所在:它不只是初始化主線中的初始化對象,一樣也是Http請求處理主線中的邏輯調度載體。
注:咱們能夠看到被咱們劃爲配置參數的那些變量都是boolean類型的,它們將在DispatcherServlet的初始化主線中起到必定的做用,咱們在以後會使用源碼進行說明。而這些boolean值能夠經過web.xml中的init-param值進行設定覆蓋(這是由HttpServletBean的特性帶來的)。
SpringMVC的運行體系
DispatcherServlet繼承結構和數據結構,實際上表述的是DispatcherServlet與另外兩大要素之間的關係:
- 繼承結構 —— DispatcherServlet與Spring容器(WebApplicationContext)之間的關係
- 數據結構 —— DispatcherServlet與組件之間的關係
因此,其實咱們能夠這麼說:SpringMVC的整個運行體系,是由DispatcherServlet、組件和容器這三者共同構成的。
在這個運行體系中,DispatcherServlet是邏輯處理的調度中心,組件則是被調度的操做對象。而容器在這裏所起到的做用,是協助DispatcherServlet更好地對組件進行管理。這就至關於一個工廠招了一大批的工人,並把工人劃分到一個統一的工做車間而便於管理。在工廠要進行生產活動時,只須要從工做車間把工人分派到相應的生產流水線上便可。
筆者在這裏引用Spring官方reference中的一幅圖,對三者之間的關係進行簡單的描述:
注:在這幅圖中,咱們除了看到在圖的左半邊DispatcherServlet、組件和容器這三者之間的調用關係之外,還能夠看到SpringMVC的運行體系與其它運行體系之間存在着關係。有關這一點,咱們在以後的討論中會詳細展開。
既然是三個元素之間的關係表述,咱們必須以兩兩關係的形式進行概括: app
- DispatcherServlet - 容器 —— DispatcherServlet對容器進行初始化
- 容器 - 組件 —— 容器對組件進行全局管理
- DispatcherServlet - 組件 —— DispatcherServlet對組件進行邏輯調用
值得注意的是,在上面這幅圖中,三大元素之間的兩兩關係其實表現得並不明顯,尤爲是「容器 - 組件」和「DispatcherServlet - 組件」之間的關係。這主要是因爲Spring官方reference所給出的這幅圖是一個靜態的關係表述,若是從動態的觀點來對整個過程加以審視,咱們就不得不將SpringMVC的運行體系與以前所提到的運行主線聯繫在一塊兒,看看這些元素在不一樣的邏輯主線中所起到的做用。
接下來,咱們就分別看看DispatcherServlet的兩條運行主線。
DispatcherServlet的初始化主線
對於DispatcherServlet的初始化主線,咱們首先應該明確幾個基本觀點:
- 初始化主線的驅動要素 —— servlet中的init方法
- 初始化主線的執行次序 —— HttpServletBean -> FrameworkServlet -> DispatcherServlet
- 初始化主線的操做對象 —— Spring容器(WebApplicationContext)和組件
這三個基本觀點,能夠說是咱們對以前全部討論的一個小結。明確了這些內容,咱們就能夠更加深刻地看看DispatcherServlet初始化主線的過程:
在這幅圖中,咱們站在一個動態的角度將DispatcherServlet、容器(WebApplicationContext)和組件這三者之間的關係表述出來,同時給出了這三者之間的運行順序和邏輯過程。讀者或許對其中的絕大多數細節還很陌生,甚至有一種無從下手的感受。這沒有關係,你們能夠首先抓住圖中的執行線,回憶一下以前有關DispatcherServlet的繼承結構和數據結構的內容。接下來,咱們就圖中的內容逐一進行解釋。
【WebApplicationContext的初始化】
以前咱們討論了DispatcherServlet對於WebApplicationContext的初始化是在FrameworkServlet中完成的,不過咱們並無細究其中的細節。在默認狀況下,這個初始化過程是由web.xml中的入口程序配置所驅動的:
1.<!-- Processes application requests -->
2.<servlet>
3. <servlet-name>dispatcher</servlet-name>
4. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
5. <load-on-startup>1</load-on-startup>
6.</servlet>
7.
8.<servlet-mapping>
9. <servlet-name>dispatcher</servlet-name>
10. <url-pattern>/**</url-pattern>
11.</servlet-mapping>
咱們已經不止一次提到過這段配置,不過在這以前都沒有對這段配置作過什麼很詳細的分析。事實上,這段入口程序的配置中隱藏了SpringMVC的兩大要素(核心分發器Dispatcher和核心配置文件[servlet-name]-servlet.xml)之間的關係表述:
downpour 寫道
在默認狀況下,web.xml配置節點中<servlet-name>的值就是創建起核心分發器DispatcherServlet與核心配置文件之間聯繫的橋樑。DispatcherServlet在初始化時會加載位置在/WEB-INF/[servlet-name]-servlet.xml的配置文件做爲SpringMVC的核心配置。
SpringMVC在這裏採用了一個「命名約定」的方法進行關係映射,這種方法很廉價也很管用。以上面的配置爲例,咱們就必須在/WEB-INF/目錄下,放一個名爲dispatcher-servlet.xml的Spring配置文件做爲SpringMVC的核心配置用以指定SpringMVC的基本組件聲明定義。
這看上去彷佛有一點彆扭,由於在實際項目中,咱們一般喜歡把配置文件放在classpath下,並使用不一樣的package進行區分。例如,在基於Maven的項目結構中,全部的配置文件應置於src/main/resources目錄下,這樣才比較符合配置文件統一化管理的最佳實踐。
因而,Spring提供了一個初始化的配置選項,經過指定contextConfigLocation選項來自定義SpringMVC核心配置文件的位置:
<!-- Processes application requests -->
2.<servlet>
3. <servlet-name>dispatcher</servlet-name>
4. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
5. <init-param>
6. <param-name>contextConfigLocation</param-name>
7. <param-value>classpath:web/applicationContext-dispatcherServlet.xml</param-value>
8. </init-param>
9. <load-on-startup>1</load-on-startup>
10.</servlet>
11.
12.<servlet-mapping>
13. <servlet-name>dispatcher</servlet-name>
14. <url-pattern>/</url-pattern>
15.</servlet-mapping>
這樣一來,DispatcherServlet在初始化時,就會自動加載在classpath下,web這個package下名爲applicationContext-dispatcherServlet.xml的文件做爲其核心配置並用以初始化容器(WebApplicationContext)。
固然,這只是DispatcherServlet在進行WebApplicationContext初始化過程當中的配置選項之一。咱們能夠在Spring的官方reference中找到相應的配置選項,有興趣的讀者能夠參照reference的說明進行嘗試:
全部的這些配置選項,實際上都是爲了讓DispatcherServlet對WebApplicationContext的初始化過程顯得更加天然。不過這只是完成了容器(WebApplicationContext)的構建工做,那麼容器所管理的那些組件,又是如何進行初始化的呢?
downpour 寫道
結論 SpringMVC核心配置文件中全部的bean定義,就是SpringMVC的組件定義,也是DispatcherServlet在初始化容器(WebApplicationContext)時,所要進行初始化的組件。
在上一篇文章咱們談到組件的時候就曾經提到,SpringMVC自身對於組件並未實現一套完整的管理機制,而是借用了Spring Framework核心框架中容器的概念,將全部的組件歸入到容器中進行管理。因此,SpringMVC的核心配置文件使用與傳統Spring Framework相同的配置形式,而整個管理的體系也是一脈相承的。
注:Spring3.0以後,單獨爲SpringMVC創建了專用的schema,從而使得咱們可使用Schema Based XML來對SpringMVC的組件進行定義。不過咱們能夠將其視做是傳統Spring配置的一個補充,而不要過於糾結不一樣的配置格式。
咱們知道,SpringMVC的組件是一個個的接口定義,當咱們在SpringMVC的核心配置文件中定義一個組件時,使用的倒是組件的實現類:
1.<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
2. <property name="prefix" value="/" />
3. <property name="suffix" value=".jsp" />
4.</bean>
這也就是Spring管理組件的模式:用具體的實現類來指定接口的行爲方式。不一樣的實現類,表明着不一樣的組件行爲模式,它們在Spring容器中是能夠共存的:
1.<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
2. <property name="prefix" value="/" />
3. <property name="suffix" value=".jsp" />
4.</bean>
5.
6.<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
7. <property name="prefix" value="/" />
8. <property name="suffix" value=".ftl" />
9.</bean>
因此,Spring的容器就像是一個聚寶盆,它只負責承載對象,管理好對象的生命週期,而並不關心一個組件接口到底有多少種實現類或者行爲模式。這也就是咱們在上面那幅圖中,畫了多個HandlerMappings、HandlerAdapters和ViewResolvers的緣由:一個組件的多種行爲模式能夠在容器中共存,容器將負責對這些實現類進行管理。而具體如何使用這些對象,則由應用程序自身來決定。
如此一來,咱們能夠大體歸納一下WebApplicationContext初始化的兩個邏輯層次:
- DispatcherServlet負責對容器(WebApplicationContext)進行初始化。
- 容器(WebApplicationContext)將讀取SpringMVC的核心配置文件進行組件的實例化。
整個過程,咱們把應用程序的日誌級別調低,能夠進行很是詳細的觀察:
引用
14:15:27,037 DEBUG StandardServletEnvironment:100 - Initializing new StandardServletEnvironment 14:15:27,128 DEBUG DispatcherServlet:115 - Initializing servlet 'dispatcher' 14:15:27,438 INFO DispatcherServlet:444 - FrameworkServlet 'dispatcher': initialization started 14:15:27,449 DEBUG DispatcherServlet:572 - Servlet with name 'dispatcher' will try to create custom WebApplicationContext context of class 'org.springframework.web.context.support.XmlWebApplicationContext', using parent context [null] 1571 [main] INFO /sample - Initializing Spring FrameworkServlet 'dispatcher' 14:15:27,505 DEBUG StandardServletEnvironment:100 - Initializing new StandardServletEnvironment 14:15:27,546 INFO XmlWebApplicationContext:495 - Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy 14:15:27,689 INFO XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [web/applicationContext-dispatcherServlet.xml] 14:15:27,872 DEBUG PluggableSchemaResolver:140 - Loading schema mappings from [META-INF/spring.schemas] 14:15:28,442 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] 14:15:28,442 DEBUG PathMatchingResourcePatternResolver:612 - Searching directory [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/com/demo2do/sample/web/controller/**/*.class] 14:15:28,450 DEBUG PathMatchingResourcePatternResolver:351 - Resolved location pattern [classpath*:com/demo2do/sample/web/controller/**/*.class] to resources [file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class], file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]] 14:15:28,569 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class] 14:15:28,571 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class] 14:15:28,634 DEBUG BeanDefinitionParserDelegate:497 - Neither XML 'id' nor 'name' specified - using generated bean name [org.springframework.web.servlet.view.InternalResourceViewResolver#0] 14:15:28,635 DEBUG XmlBeanDefinitionReader:216 - Loaded 19 bean definitions from location pattern [classpath:web/applicationContext-dispatcherServlet.xml] 14:15:28,635 DEBUG XmlWebApplicationContext:525 - Bean factory for WebApplicationContext for namespace 'dispatcher-servlet': org.springframework.beans.factory.support.DefaultListableBeanFactory@2321b59a: defining beans [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,blogController,userController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0,org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; root of factory hierarchy 14:15:29,015 DEBUG RequestMappingHandlerMapping:98 - Looking for request mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy 14:15:29,037 INFO RequestMappingHandlerMapping:188 - Mapped "{[/blog],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.station.web.controller.BlogController.index() 14:15:29,039 INFO RequestMappingHandlerMapping:188 - Mapped "{[/login],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.station.web.controller.UserController.login(java.lang.String,java.lang.String) 14:15:29,040 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0' 14:15:29,460 DEBUG BeanNameUrlHandlerMapping:71 - Looking for URL mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:15:27 CST 2012]; root of context hierarchy 14:15:29,539 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0' 14:15:29,540 DEBUG DefaultListableBeanFactory:217 - Creating shared instance of singleton bean 'org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0' 14:15:29,555 INFO SimpleUrlHandlerMapping:314 - Mapped URL path [/static/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0' 14:15:29,556 DEBUG DefaultListableBeanFactory:458 - Finished creating instance of bean 'org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0' 14:15:29,827 DEBUG DispatcherServlet:523 - Published WebApplicationContext of servlet 'dispatcher' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher] 14:15:29,827 INFO DispatcherServlet:463 - FrameworkServlet 'dispatcher': initialization completed in 2389 ms 14:15:29,827 DEBUG DispatcherServlet:136 - Servlet 'dispatcher' configured successfully 4047 [main] INFO org.mortbay.log - Started SelectChannelConnector@0.0.0.0:8080 Jetty Server started, use 4267 ms
在這段啓動日誌(筆者進行了必定刪減)中,筆者刻意將WebApplicationContext的初始化的標誌日誌使用紅色的標進行區分,而將核心配置文件的讀取位置和組件定義初始化的標誌日誌使用藍色標記加以區分。相信有了這段日誌的幫助,讀者應該能夠對WebApplicationContext的初始化過程有了更加直觀的認識。
注:啓動日誌是咱們研究SpringMVC的主要途徑之一,以後咱們還將反覆使用這種方法對SpringMVC的運行過程進行研究。讀者應該仔細品味每一條日誌的做用,從而可以與以後的分析講解呼應起來。
或許讀者對WebApplicationContext對組件進行初始化的過程還有點困惑,你們不妨先將這個過程省略,把握住整個DispatcherServlet的大方向。咱們在以後的文章中,還將對SpringMVC的組件、這些組件的定義以及組件的初始化方式作進一步的分析和探討。
到此爲止,圖中順着FrameworkServlet的那些箭頭,咱們已經交代清楚,讀者能夠回味一下整個過程。
【獨立的WebApplicationContext體系】
獨立的WebApplicationContext體系,是SpringMVC初始化主線中的一個很是重要的概念。回顧一下剛纔曾經提到過的DispatcherServlet、容器和組件三者之間的關係,咱們在引用的那副官方reference的示意圖中,實際上已經包含了這一層意思:
downpour 寫道
結論 在DispatcherServlet初始化的過程當中所構建的WebApplicationContext獨立於Spring自身的所構建的其餘WebApplicationContext體系而存在。
稍有一些Spring編程經驗的程序員,對於下面的配置應該很是熟悉:
1.<context-param>
2. <param-name>contextConfigLocation</param-name>
3. <param-value>classpath:context/applicationContext-*.xml</param-value>
4.</context-param>
5.
6.<listener>
7. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
8.</listener>
在上面的代碼中,咱們定義了一個Listener,它會在整個Web應用程序啓動的時候運行一次,並初始化傳統意義上的Spring的容器。這也是通常狀況下,當並不使用SpringMVC做爲咱們的表示層解決方案,卻但願在咱們的Web應用程序中使用Spring相關功能時所採起的一種配置方式。
若是咱們要在這裏引入SpringMVC,整個配置看上去就像這樣:
1.<context-param>
2. <param-name>contextConfigLocation</param-name>
3. <param-value>classpath:context/applicationContext-*.xml</param-value>
4.</context-param>
5.
6.<listener>
7. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
8.</listener>
9.
10.<!-- Processes application requests -->
11.<servlet>
12. <servlet-name>dispatcher</servlet-name>
13. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
14. <init-param>
15. <param-name>contextConfigLocation</param-name>
16. <param-value>classpath:web/applicationContext-dispatcherServlet.xml</param-value>
17. </init-param>
18. <load-on-startup>1</load-on-startup>
19.</servlet>
20.
21.<servlet-mapping>
22. <servlet-name>dispatcher</servlet-name>
23. <url-pattern>/</url-pattern>
24.</servlet-mapping>
在這種狀況下,DispatcherServlet和ContextLoaderListener會分別構建不一樣做用範圍的容器(WebApplicationContext)。咱們能夠引入兩個不一樣的概念來對其進行表述:ContextLoaderListener所初始化的容器,咱們稱之爲Root WebApplicationContext;而DispatcherServlet所初始化的容器,是SpringMVC WebApplicationContext。
一樣採起日誌分析的方法,加入了ContextLoaderListener以後,整個啓動日誌變成了這樣:
引用
[main] INFO /sample - Initializing Spring root WebApplicationContext 14:56:42,261 INFO ContextLoader:272 - Root WebApplicationContext: initialization started 14:56:42,343 DEBUG StandardServletEnvironment:100 - Initializing new StandardServletEnvironment 14:56:42,365 INFO XmlWebApplicationContext:495 - Refreshing Root WebApplicationContext: startup date [Mon Feb 06 14:56:42 CST 2012]; root of context hierarchy 14:56:42,441 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\context] 14:56:42,442 DEBUG PathMatchingResourcePatternResolver:612 - Searching directory [D:\Work\Demo2do\Sample\target\classes\context] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/context/applicationContext-*.xml] 14:56:42,446 DEBUG PathMatchingResourcePatternResolver:351 - Resolved location pattern [classpath:context/applicationContext-*.xml] to resources [file [D:\Work\Demo2do\Sample\target\classes\context\applicationContext-configuration.xml]] 14:56:42,447 INFO XmlBeanDefinitionReader:315 - Loading XML bean definitions from file [D:\Work\Demo2do\Sample\target\classes\context\applicationContext-configuration.xml] 14:56:42,486 DEBUG PluggableSchemaResolver:140 - Loading schema mappings from [META-INF/spring.schemas] 14:56:42,597 DEBUG DefaultBeanDefinitionDocumentReader:108 - Loading bean definitions 14:56:42,658 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample] 14:56:42,699 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\service\impl\BlogServiceImpl.class] 14:56:42,750 DEBUG XmlBeanDefinitionReader:216 - Loaded 5 bean definitions from location pattern [classpath:context/applicationContext-*.xml] 14:56:42,750 DEBUG XmlWebApplicationContext:525 - Bean factory for Root WebApplicationContext: org.springframework.beans.factory.support.DefaultListableBeanFactory@478e4327: defining beans [blogService,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor]; root of factory hierarchy 14:56:42,860 DEBUG ContextLoader:296 - Published root WebApplicationContext as ServletContext attribute with name [org.springframework.web.context.WebApplicationContext.ROOT] 14:56:42,860 INFO ContextLoader:301 - Root WebApplicationContext: initialization completed in 596 ms
14:56:42,935 DEBUG DispatcherServlet:115 - Initializing servlet 'dispatcher' 14:56:42,974 INFO DispatcherServlet:444 - FrameworkServlet 'dispatcher': initialization started 14:56:42,974 DEBUG DispatcherServlet:572 - Servlet with name 'dispatcher' will try to create custom WebApplicationContext context of class 'org.springframework.web.context.support.XmlWebApplicationContext', using parent context [Root WebApplicationContext: startup date [Mon Feb 06 14:56:42 CST 2012]; root of context hierarchy] 14:56:42,979 INFO XmlWebApplicationContext:495 - Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext 14:56:42,983 INFO XmlBeanDefinitionReader:315 - Loading XML bean definitions from class path resource [web/applicationContext-dispatcherServlet.xml] 14:56:42,987 DEBUG PluggableSchemaResolver:140 - Loading schema mappings from [META-INF/spring.schemas] 14:56:43,035 DEBUG DefaultBeanDefinitionDocumentReader:108 - Loading bean definitions 14:56:43,075 DEBUG PathMatchingResourcePatternResolver:550 - Looking for matching resources in directory tree [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] 14:56:43,075 DEBUG PathMatchingResourcePatternResolver:612 - Searching directory [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller] for files matching pattern [D:/Work/Demo2do/Sample/target/classes/com/demo2do/sample/web/controller/**/*.class] 14:56:43,077 DEBUG PathMatchingResourcePatternResolver:351 - Resolved location pattern [classpath*:com/demo2do/sample/web/controller/**/*.class] to resources [file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class], file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class]] 14:56:43,079 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\BlogController.class] 14:56:43,080 DEBUG ClassPathBeanDefinitionScanner:243 - Identified candidate component class: file [D:\Work\Demo2do\Sample\target\classes\com\demo2do\sample\web\controller\UserController.class] 14:56:43,089 DEBUG XmlBeanDefinitionReader:216 - Loaded 19 bean definitions from location pattern [classpath:web/applicationContext-dispatcherServlet.xml] 14:56:43,089 DEBUG XmlWebApplicationContext:525 - Bean factory for WebApplicationContext for namespace 'dispatcher-servlet': org.springframework.beans.factory.support.DefaultListableBeanFactory@5e6458a6: defining beans [org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0,org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0,org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,blogController,userController,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0,org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@478e4327 14:56:43,323 DEBUG RequestMappingHandlerMapping:98 - Looking for request mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext 14:56:43,345 INFO RequestMappingHandlerMapping:188 - Mapped "{[/blog],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.BlogController.index() 14:56:43,346 INFO RequestMappingHandlerMapping:188 - Mapped "{[/login],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.web.servlet.ModelAndView com.demo2do.sample.web.controller.UserController.login(java.lang.String,java.lang.String) 14:56:43,707 DEBUG BeanNameUrlHandlerMapping:71 - Looking for URL mappings in application context: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext 14:56:43,828 INFO SimpleUrlHandlerMapping:314 - Mapped URL path [/static/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0' 14:56:43,883 DEBUG DispatcherServlet:523 - Published WebApplicationContext of servlet 'dispatcher' as ServletContext attribute with name [org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcher] 14:56:43,883 INFO DispatcherServlet:463 - FrameworkServlet 'dispatcher': initialization completed in 909 ms 14:56:43,883 DEBUG DispatcherServlet:136 - Servlet 'dispatcher' configured successfully 2687 [main] INFO org.mortbay.log - Started SelectChannelConnector@0.0.0.0:8080 Jetty Server started, use 2901 ms
整個啓動日誌被咱們分爲了2段。第一段的過程初始化的是Root WebApplicationContext;而第二段的過程初始化的是SpringMVC的WebApplicationContext。咱們仍是使用了紅色的標記和藍色標記指出了在整個初始化過程當中的一些重要事件。其中,有這樣一段內容值得咱們注意:
引用
14:56:42,979 INFO XmlWebApplicationContext:495 - Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Mon Feb 06 14:56:42 CST 2012]; parent: Root WebApplicationContext
在這段日誌中,很是明確地指出了SpringMVC WebApplicationContext與Root WebApplicationContext之間的關係:從屬關係。由於根據這段日誌的表述,SpringMVC WebApplicationContext可以感知到Root WebApplicationContext的存在,而且將其做爲parent容器。
Spring正是使用這種Parent-Child的容器關係來對不一樣的編程層次進行劃分。這種咱們俗稱的父子關係實際上不只僅是一種從屬關係,更是一種引用關係。從剛纔的日誌分析中,咱們能夠看出:SpringMVC中所定義的一切組件可以無縫地與Root WebApplicationContext中的組件整合。
到此爲止,咱們針對圖中以web.xml爲核心的箭頭分支進行了講解,讀者能夠將圖中的內容與上面的文字說明對照再次加以理解。
【組件默認行爲的指定】
DispatcherServlet的初始化主線的執行體系是順着其繼承結構依次進行的,咱們在以前曾經討論過它的執行次序。因此,只有在FrameworkServlet完成了對於WebApplicationContext和組件的初始化以後,執行權才被正式轉移到DispatcherServlet中。咱們能夠來看看DispatcherServlet此時究竟幹了哪些事:
1./**
2. * This implementation calls {@link #initStrategies}.
3. */
4.@Override
5.protected void onRefresh(ApplicationContext context) {
6. initStrategies(context);
7.}
8.
9./**
10. * Initialize the strategy objects that this servlet uses.
11. * <p>May be overridden in subclasses in order to initialize further strategy objects.
12. */
13.protected void initStrategies(ApplicationContext context) {
14. initMultipartResolver(context);
15. initLocaleResolver(context);
16. initThemeResolver(context);
17. initHandlerMappings(context);
18. initHandlerAdapters(context);
19. initHandlerExceptionResolvers(context);
20. initRequestToViewNameTranslator(context);
21. initViewResolvers(context);
22. initFlashMapManager(context);
23.}
onRefresh是FrameworkServlet中預留的擴展方法,在DispatcherServlet中作了一個基本實現:initStrategies。咱們粗略一看,很容易就能明白DispatcherServlet到底在這裏幹些什麼了:初始化組件。
讀者或許會問,組件不是已經在WebApplicationContext初始化的時候已經被初始化過了嘛?這裏所謂的組件初始化,指的又是什麼呢?讓咱們來看看其中的一個方法的源碼:
1./**
2. * Initialize the MultipartResolver used by this class.
3. * <p>If no bean is defined with the given name in the BeanFactory for this namespace,
4. * no multipart handling is provided.
5. */
6.private void initMultipartResolver(ApplicationContext context) {
7. try {
8. this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
9. if (logger.isDebugEnabled()) {
10. logger.debug("Using MultipartResolver [" + this.multipartResolver + "]");
11. }
12. } catch (NoSuchBeanDefinitionException ex) {
13. // Default is no multipart resolver.
14. this.multipartResolver = null;
15. if (logger.isDebugEnabled()) {
16. logger.debug("Unable to locate MultipartResolver with name '" + MULTIPART_RESOLVER_BEAN_NAME +
17. "': no multipart request handling provided");
18. }
19. }
20.}
原來,這裏的初始化,指的是DispatcherServlet從容器(WebApplicationContext)中讀取組件的實現類,並緩存於DispatcherServlet內部的過程。還記得咱們以前給出的DispatcherServlet的數據結構嗎?這些位於DispatcherServlet內部的組件實際上只是一些來源於容器緩存實例,不過它們一樣也是DispatcherServlet進行後續操做的基礎。
注:咱們在第一篇文章中就曾經提到過Servlet實例內部的屬性的訪問有線程安全問題。而在這裏,咱們能夠看到全部的組件都以Servlet內部屬性的形式被調用,充分證明了這些組件自己也都是無狀態的單例對象,因此咱們在這裏沒必要考慮線程安全的問題。
若是對上面的代碼加以詳細分析,咱們會發現initMultipartResolver的過程是查找特定MultipartResolver實現類的過程。由於在容器中查找組件的時候,採起的是根據特定名稱(MULTIPART_RESOLVER_BEAN_NAME)進行查找的策略。由此,咱們能夠看到DispatcherServlet進行組件初始化的特色:
downpour 寫道
結論 DispatcherServlet中對於組件的初始化過程其實是應用程序在WebApplicationContext中選擇和查找組件實現類的過程,也是指定組件在SpringMVC中的默認行爲方式的過程。
除了根據特定名稱進行查找的策略之外,咱們還對DispatcherServlet中指定SpringMVC默認行爲方式的其餘的策略進行的總結:
- 名稱查找 —— 根據bean的名字在容器中查找相應的實現類
- 自動搜索 —— 自動搜索容器中全部某個特定組件(接口)的全部實現類
- 默認配置 —— 根據一個默認的配置文件指定進行實現類加載
這三條策略恰巧在initHandlerMappings的過程當中都有體現,讀者能夠從其源碼中找到相應的線索:
private void initHandlerAdapters(ApplicationContext context) {
2. this.handlerAdapters = null;
3.
4. if (this.detectAllHandlerAdapters) {
5. // Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
6. Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
7. if (!matchingBeans.isEmpty()) {
8. this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values());
9. // We keep HandlerAdapters in sorted order.
10. OrderComparator.sort(this.handlerAdapters);
11. }
12. }
13. else {
14. try {
15. HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
16. this.handlerAdapters = Collections.singletonList(ha);
17. }
18. catch (NoSuchBeanDefinitionException ex) {
19. // Ignore, we'll add a default HandlerAdapter later.
20. }
21. }
22.
23. // Ensure we have at least some HandlerAdapters, by registering
24. // default HandlerAdapters if no other adapters are found.
25. if (this.handlerAdapters == null) {
26. this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
27. if (logger.isDebugEnabled()) {
28. logger.debug("No HandlerAdapters found in servlet '" + getServletName() + "': using default");
29. }
30. }
31.}
這裏有必要對「默認策略」作一個簡要的說明。SpringMVC爲一些核心組件設置了默認行爲方式的說明,這個說明以一個properties文件的形式位於SpringMVC分發包(例如spring-webmvc-3.1.0.RELEASE.jar)的內部:
咱們能夠觀察一下DispatcherServlet.properties的內容:
引用
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.DefaultFlashMapManager
結合剛纔initHandlerMappings的源碼,咱們能夠發現若是沒有開啓detectAllHandlerAdapters選項或者根據HANDLER_ADAPTER_BEAN_NAME的名稱沒有找到相應的組件實現類,就會使用DispatcherServlet.properties文件中對於HandlerMapping接口的實現來進行組件默認行爲的初始化。
因而可知,DispatcherServlet.properties中所指定的全部接口的實現方式在Spring的容器WebApplicationContext中總有相應的定義。這一點,咱們在組件的討論中還會詳談。
這個部分咱們的側重點是圖中DispatcherServlet與容器之間的關係。讀者須要理解的是圖中爲何會有兩份組件定義,它們之間的區別在哪裏,以及DispatcherServlet在容器中查找組件的三種策略。
小結
在本文中,咱們對SpringMVC的核心類:DispatcherServlet進行了一番梳理。也對整個SpringMVC的兩條主線之一的初始化主線作了詳細的分析。
對於DispatcherServlet而言,重要的其實並非這個類中的代碼和邏輯,而是應該掌握這個類在整個框架中的做用以及與SpringMVC中其餘要素的關係。
對於初始化主線而言,核心其實僅僅在於那張筆者爲你們精心打造的圖。讀者只要掌握了這張圖,相信對整個SpringMVC的初始化過程會有一個全新的認識。