SpringMVC原始碼剖析(三)- DispatcherServlet的初始化流
在我們第一次學Servlet程式設計,學java web的時候,還沒有那麼多框架。我們開發一個簡單的功能要做的事情很簡單,就是繼承HttpServlet,根據需要重寫一下doGet,doPost方法,跳轉到我們定義好的jsp頁面。Servlet類編寫完之後在web.xml裡註冊這個Servlet類。
除此之外,沒有其他了。我們啟動web伺服器,在瀏覽器中輸入地址,就可以看到瀏覽器上輸出我們寫好的頁面。為了更好的理解上面這個過程,你需要學習關於Servlet生命週期的三個階段,就是所謂的“init-service-destroy”。
以上的知識,我覺得對於你理解SpringMVC的設計思想,已經足夠了。SpringMVC當然可以稱得上是一個複雜的框架,但是同時它又遵循Servlet世界裡最簡單的法則,那就是“init-service-destroy”。我們要分析SpringMVC的初始化流程,其實就是分析DispatcherServlet類的init()方法,讓我們帶著這種單純的觀點,開啟DispatcherServlet的原始碼一窺究竟吧。
1.<init-param>配置元素讀取
用Eclipse IDE開啟DispatcherServlet類的原始碼,ctrl+T看一下。
DispatcherServlet類的初始化入口方法init()定義在HttpServletBean這個父類中,HttpServletBean類作為一個直接繼承於HttpServlet類的類,覆寫了HttpServlet類的init()方法,實現了自己的初始化行為。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@Override public
final
void
init() throws
ServletException { if
(logger.isDebugEnabled()) { logger.debug( "Initializing
servlet '"
+ getServletName() + "'" ); } //
Set bean properties from init parameters. try
{ PropertyValues
pvs = new
ServletConfigPropertyValues(getServletConfig(), this .requiredProperties); BeanWrapper
bw = PropertyAccessorFactory.forBeanPropertyAccess( this ); ResourceLoader
resourceLoader = new
ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource. class ,
new
ResourceEditor(resourceLoader, this .environment)); initBeanWrapper(bw); bw.setPropertyValues(pvs,
true ); } catch
(BeansException ex) { logger.error( "Failed
to set bean<span></span> properties on servlet '"
+ getServletName() + "'" ,
ex); throw
ex; } //
Let subclasses do whatever initialization they like. initServletBean(); if
(logger.isDebugEnabled()) { logger.debug( "Servlet
'"
+ getServletName() + "'
configured successfully" ); } } |
這裡的initServletBean()方法在HttpServletBean類中是一個沒有任何實現的空方法,它的目的就是留待子類實現自己的初始化邏輯,也就是我們常說的模板方法設計模式。SpringMVC在此生動的運用了這個模式,init()方法就是模版方法模式中的模板方法,SpringMVC真正的初始化過程,由子類FrameworkServlet中覆寫的initServletBean()方法觸發。
再看一下init()方法內被try,catch塊包裹的程式碼,裡面涉及到BeanWrapper,PropertyValues,ResourceEditor這些Spring內部非常底層的類。要深究具體程式碼實現上面的細節,需要對Spring框架原始碼具有相當深入的瞭解。我們這裡先避繁就簡,從程式碼效果和設計思想上面來分析這段try,catch塊內的程式碼所做的事情:
- 註冊一個字串到資原始檔的編輯器,讓Servlet下面的<init-param>配置元素可以使用形如“classpath:”這種方式指定SpringMVC框架bean配置檔案的來源。
- 將web.xml中在DispatcherServlet這個Servlet下面的<init-param>配置元素利用JavaBean的方式(即通過setter方法)讀取到DispatcherServlet中來。
這兩點,我想通過下面一個例子來說明一下。
我在web.xml中註冊的DispatcherServlet配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<!--
springMVC配置開始 --> < servlet > < servlet-name >appServlet</ servlet-name > < servlet-class >org.springframework.web.servlet.DispatcherServlet</ servlet-class > < init-param > < param-name >contextConfigLocation</ param-name > < param-value >classpath:spring/spring-servlet.xml</ param-value > </ init-param > < load-on-startup >1</ load-on-startup > </ servlet > < servlet-mapping > < servlet-name >appServlet</ servlet-name > < url-pattern >/</ url-pattern > </ servlet-mapping > <!--
springMVC配置結束 --> |
可以看到,我註冊了一個名為contextConfigLocation的<init-param>元素,其值為“classpath:spring/spring-servlet.xml”,這也是大家常常用來指定SpringMVC配置檔案路徑的方法。上面那段try,catch塊包裹的程式碼發揮的作用,一個是將“classpath:spring/spring-servlet.xml”這段字串轉換成classpath路徑下的一個資原始檔,供框架初始化讀取配置元素。在我的工程中是在spring資料夾下面的配置檔案spring-servlet.xml。
另外一個作用,就是將contextConfigLocation的值讀取出來,然後通過setContextConfigLocation()方法設定到DispatcherServlet中,這個setContextConfigLocation()方法是在FrameworkServlet類中定義的,也就是上面繼承類圖中DispatcherServlet的直接父類。
我們在setContextConfigLocation()方法上面打上一個斷點,啟動web工程,可以看到下面的除錯結果。
HttpServletBean類的作者是大名鼎鼎的Spring之父Rod Johnson。作為POJO程式設計哲學的大師,他在HttpServletBean這個類的設計中,運用了依賴注入思想完成了<init-param>配置元素的讀取。他抽離出HttpServletBean這個類的目的也在於此,就是“以依賴注入的方式來讀取Servlet類的<init-param>配置資訊”,而且這裡很明顯是一種setter注入。
明白了HttpServletBean類的設計思想,我們也就知道可以如何從中獲益。具體來說,我們繼承HttpServletBean類(就像DispatcherServlet做的那樣),在類中定義一個屬性,為這個屬性加上setter方法後,我們就可以在<init-param>元素中為其定義值。在類被初始化後,值就會被注入進來,我們可以直接使用它,避免了樣板式的getInitParameter()方法的使用,而且還免費享有Spring中資源編輯器的功能,可以在web.xml中,通過“classpath:”直接指定類路徑下的資原始檔。
注意,雖然SpringMVC本身為了後面初始化上下文的方便,使用了字串來宣告和設定contextConfigLocation引數,但是將其宣告為Resource型別,同樣能夠成功獲取。鼓勵讀者們自己繼承HttpServletBean寫一個測試用的Servlet類,並設定一個引數來除錯一下,這樣能夠幫助你更好的理解獲取配置引數的過程。
2.容器上下文的建立
上一篇文章中提到過,SpringMVC使用了Spring容器來容納自己的配置元素,擁有自己的bean容器上下文。在SpringMVC初始化的過程中,非常關鍵的一步就是要建立起這個容器上下文,而這個建立上下文的過程,發生在FrameworkServlet類中,由上面init()方法中的initServletBean()方法觸發。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
@Override protected
final
void
initServletBean() throws
ServletException { getServletContext().log( "Initializing
Spring FrameworkServlet '"
+ getServletName() + "'" ); if
( this .logger.isInfoEnabled())
{ this .logger.info( "FrameworkServlet
'"
+ getServletName() + "':
initialization started" ); } long
startTime = System.currentTimeMillis(); try
{ this .webApplicationContext
= initWebApplicationContext(); initFrameworkServlet(); } catch
(ServletException ex) { this .logger.error( "Context
initialization failed" ,
ex); throw
ex; } catch
(RuntimeException ex) { this .logger.error( "Context
initialization failed" ,
ex); throw
ex; } if
( this .logger.isInfoEnabled())
{ long
elapsedTime = System.currentTimeMillis() - startTime; this .logger.info( "FrameworkServlet
'"
+ getServletName() + "':
initialization completed in "
+ elapsedTime
+ "
ms" ); } } |
1
|
this .webApplicationContext
= initWebApplicationContext(); |
initWebApplicationContext()方法,封裝了建立Spring容器上下文的整個過程,方法內的邏輯如下:
- 獲取由ContextLoaderListener初始化並註冊在ServletContext中的根上下文,記為rootContext
- 如果webApplicationContext已經不為空,表示這個Servlet類是通過程式設計式註冊到容器中的(Servlet 3.0+中的ServletContext.addServlet() ),上下文也由程式設計式傳入。若這個傳入的上下文還沒被初始化,將rootContext上下文設定為它的父上下文,然後將其初始化,否則直接使用。
- 通過wac變數的引用是否為null,判斷第2步中是否已經完成上下文的設定(即上下文是否已經用程式設計式方式傳入),如果wac==null成立,說明該Servlet不是由程式設計式註冊到容器中的。此時以contextAttribute屬性的值為鍵,在ServletContext中查詢上下文,查詢得到,說明上下文已經以別的方式初始化並註冊在contextAttribute下,直接使用。
- 檢查wac變數的引用是否為null,如果wac==null成立,說明2、3兩步中的上下文初始化策略都沒成功,此時呼叫createWebApplicationContext(rootContext),建立一個全新的以rootContext為父上下文的上下文,作為SpringMVC配置元素的容器上下文。大多數情況下我們所使用的上下文,就是這個新建的上下文。
- 以上三種初始化上下文的策略,都會回撥onRefresh(ApplicationContext context)方法(回撥的方式根據不同策略有不同),onRefresh方法在DispatcherServlet類中被覆寫,以上面得到的上下文為依託,完成SpringMVC中預設實現類的初始化。
- 最後,將這個上下文釋出到ServletContext中,也就是將上下文以一個和Servlet類在web.xml中註冊名字有關的值為鍵,設定為ServletContext的一個屬性。你可以通過改變publishContext的值來決定是否釋出到ServletContext中,預設為true。
以上面6點跟蹤FrameworkServlet類中的程式碼,可以比較清晰的瞭解到整個容器上下文的建立過程,也就能夠領會到FrameworkServlet類的設計目的,它是用來建立一個和Servlet關聯的Spring容器上下文,並將其註冊到ServletContext中的。跳脫開SpringMVC體系,我們也能通過繼承FrameworkServlet類,得到與Spring容器整合的好處,FrameworkServlet和HttpServletBean一樣,是一個可以獨立使用的類。整個SpringMVC設計中,處處體現開閉原則,這裡顯然也是其中一點。
3.初始化SpringMVC預設實現類
初始化流程在FrameworkServlet類中流轉,建立了上下文後,通過onRefresh(ApplicationContext context)方法的回撥,進入到DispatcherServlet類中。
1
2
3
4
|
@Override protected
void
onRefresh(ApplicationContext context) { initStrategies(context); } |
DispatcherServlet類覆寫了父類FrameworkServlet中的onRefresh(ApplicationContext context)方法,提供了SpringMVC各種程式設計元素的初始化。當然這些程式設計元素,都是作為容器上下文中一個個bean而存在的。具體的初始化策略,在initStrategies()方法中封裝。
1
2
3
4
5
6
7
8
9
10
11
|
protected
void
initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
private
void
initHandlerMappings(ApplicationContext context) { this .handlerMappings
= null ; if
( this .detectAllHandlerMappings)
{ //
Find all HandlerMappings in the ApplicationContext, including ancestor contexts. Map<String,
HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context,
HandlerMapping. class ,
true ,
false ); if
(!matchingBeans.isEmpty()) { this .handlerMappings
= new
ArrayList<HandlerMapping>(matchingBeans.values()); //
We keep HandlerMappings in sorted order. OrderComparator.sort( this .handlerMappings); } } else
{ try
{ HandlerMapping
hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping. class ); this .handlerMappings
= Collections.singletonList(hm); } catch
(NoSuchBeanDefinitionException ex) { //
Ignore, we'll add a default HandlerMapping later. } } //
Ensure we have at least one HandlerMapping, by registering //
a default HandlerMapping if no other mappings are found. if
( this .handlerMappings
== null )
{ this .handlerMappings
= getDefaultStrategies(context, HandlerMapping. class ); if
(logger.isDebugEnabled()) { logger.debug( "No
HandlerMappings found in servlet '"
+ getServletName() + "':
using default" ); } } } |
detectAllHandlerMappings變數預設為true,所以在初始化HandlerMapping介面預設實現類的時候,會把上下文中所有HandlerMapping型別的Bean都註冊在handlerMappings這個List變數中。如果你手工將其設定為false,那麼將嘗試獲取名為handlerMapping的Bean,新建一個只有一個元素的List,將其賦給handlerMappings。如果經過上面的過程,handlerMappings變數仍為空,那麼說明你沒有在上下文中提供自己HandlerMapping型別的Bean定義。此時,SpringMVC將採用預設初始化策略來初始化handlerMappings。
點進去getDefaultStrategies看一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@SuppressWarnings ( "unchecked" ) protected
<T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { String
key = strategyInterface.getName(); String
value = defaultStrategies.getProperty(key); if
(value != null )
{ String[]
classNames = StringUtils.commaDelimitedListToStringArray(value); List<T>
strategies = new
ArrayList<T>(classNames.length); for
(String className : classNames) { try
{ Class<?>
clazz = ClassUtils.forName(className, DispatcherServlet. class .getClassLoader()); Object
strategy = createDefaultStrategy(context, clazz); strategies.add((T)
strategy); } catch
(ClassNotFoundException ex) { throw
new
BeanInitializationException( "Could
not find DispatcherServlet's default strategy class ["
+ className + "]
for interface ["
+ key + "]" ,
ex); } catch
(LinkageError err) { throw
new
BeanInitializationException( "Error
loading DispatcherServlet's default strategy class ["
+ className + "]
for interface ["
+ key + "]:
problem with class file or dependent class" ,
err); } } return
strategies; } else
{ return
new
LinkedList<T>(); } } |
它是一個範型的方法,承擔所有SpringMVC程式設計元素的預設初始化策略。方法的內容比較直白,就是以傳遞類的名稱為鍵,從defaultStrategies這個Properties變數中獲取實現類,然後反射初始化。
需要說明一下的是defaultStrategies變數的初始化,它是在DispatcherServlet的靜態初始化程式碼塊中載入的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private
static
final
Properties defaultStrategies; static
{ //
Load default strategy implementations from properties file. //
This is currently strictly internal and not meant to be customized //
by application developers. try
{ ClassPathResource
resource = new
ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet. class ); defaultStrategies
= PropertiesLoaderUtils.loadProperties(resource); } catch
(IOException ex) { throw
new
IllegalStateException( "Could
not load 'DispatcherServlet.properties': "
+ ex.getMessage()); } } |
1
|
private
static
final
String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties" ; |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
#
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.SessionFlashMapManager |
至此,我們分析完了initHandlerMappings(context)方法的執行過程,其他的初始化過程與這個方法非常類似。所有初始化方法執行完後,SpringMVC正式完成初始化,靜靜等待Web請求的到來。
4.總結
回顧整個SpringMVC的初始化流程,我們看到,通過HttpServletBean、FrameworkServlet、DispatcherServlet三個不同的類層次,SpringMVC的設計者將三種不同的職責分別抽象,運用模版方法設計模式分別固定在三個類層次中。其中HttpServletBean完成的是<init-param>配置元素的依賴注入,FrameworkServlet完成的是容器上下文的建立,DispatcherServlet完成的是SpringMVC具體程式設計元素的初始化策略。
轉載:http://my.oschina.net/lichhao/blog/102315
相關文章
- SpringMVC DispatcherServlet原始碼解析SpringMVCServlet原始碼
- springMVC DispatcherServlet 初始化SpringMVCServlet
- SpringMVC原始碼分析3:DispatcherServlet的初始化與請求轉發SpringMVC原始碼Servlet
- SpringMVC原始碼解析系列2-DispatcherServletSpringMVC原始碼Servlet
- springmvc原始碼 ---DispatcherServlet 處理請求SpringMVC原始碼Servlet
- SpringMVC原始碼分析2:SpringMVC設計理念與DispatcherServletSpringMVC原始碼Servlet
- SpringMVC 解析(二)DispatcherServletSpringMVCServlet
- Spring原始碼分析(四)SpringMVC初始化原始碼SpringMVC
- Spring框架系列(13) - SpringMVC實現原理之DispatcherServlet的初始化過程框架SpringMVCServlet
- springMVC DispatcherServlet 工作流程SpringMVCServlet
- jQuery原始碼剖析(三) - Callbacks 原理分析jQuery原始碼
- Flutter Dio原始碼分析(三)--深度剖析Flutter原始碼
- Java集合原始碼剖析——ArrayList原始碼剖析Java原始碼
- kafka原始碼剖析(三)之日誌管理-LogManagerKafka原始碼
- SpringMVC原始碼分析1:SpringMVC概述SpringMVC原始碼
- SpringMVC原始碼解析SpringMVC原始碼
- SpringMVC原始碼分析SpringMVC原始碼
- SpringMVC原始碼剖析5:訊息轉換器HttpMessageConverter與@ResponseBody註解SpringMVC原始碼HTTP
- Spring原始碼剖析9:Spring事務原始碼剖析Spring原始碼
- OkHttp 原始碼剖析系列(三)——快取機制HTTP原始碼快取
- mybatis、spring、springMVC、springboot的原始碼MyBatisSpringMVCSpring Boot原始碼
- epoll–原始碼剖析原始碼
- Thread原始碼剖析thread原始碼
- Handler原始碼剖析原始碼
- HashMap原始碼剖析HashMap原始碼
- SpringMVC原始碼分析原理SpringMVC原始碼
- 我的原始碼閱讀之路:redux原始碼剖析原始碼Redux
- Graphx 原始碼剖析-圖的生成原始碼
- 精盡MyBatis原始碼分析 - MyBatis初始化(三)之 SQL 初始化(上)MyBatis原始碼SQL
- Kafka 原始碼剖析(一)Kafka原始碼
- Flutter 原始碼剖析(一)Flutter原始碼
- 全面剖析 Redux 原始碼Redux原始碼
- 深入剖析LinkedList原始碼原始碼
- Java LinkedList 原始碼剖析Java原始碼
- vue原始碼剖析(一)Vue原始碼
- springMvc原始碼解讀–AbstractUrlHandlerMappingSpringMVC原始碼APP
- Java Activiti 工作流引擎 springmvc SSM 流程審批 後臺框架原始碼JavaSpringMVCSSM框架原始碼
- 08.ElementUI 2.X 原始碼學習:原始碼剖析之工程化(三)UI原始碼
- OC原始碼剖析物件的本質原始碼物件