01-SpringMVC啟動(dòng)過程分析
我們使用Servlet3.0的方式來配置DispatcherServlet,同樣還是采用5.1.4.RELEASE版本的Spring。根據(jù)Spring官方文檔的介紹,我們可以利用下面這段代碼來配置

首先我來解釋一下,在Servlet3.0的時(shí)候提出了可以通過SPI的方式動(dòng)態(tài)注冊(cè)一個(gè)Servlet,Spring正是利用這一點(diǎn),通過SpringServletContainerInitializer來啟動(dòng)WebApplicationInitializer的實(shí)現(xiàn)類,因此我們可以通過實(shí)現(xiàn)WebApplicationInitializer接口,達(dá)到注冊(cè)DispatcherServlet給Servlet容器(本文使用Tomcat)。從圖中可以看到,一開始會(huì)實(shí)例化一個(gè)Spring容器(AnnotationConfigWebApplicationContext),并利用這個(gè)ApplicationContext來實(shí)例化DispatcherServlet。
首先我們來回顧一下Java中類的實(shí)例化過程
- 初始化父類靜態(tài)變量(靜態(tài)變量,靜態(tài)代碼塊,靜態(tài)方法,需要注意的是main方法也是靜態(tài)的)
- 初始化子類靜態(tài)變量(范圍同父類)
- 初始化父類普通成員變量及方法
- 調(diào)用父類構(gòu)造方法
- 初始化子類普通成員變量及方法
- 調(diào)用子類構(gòu)造方法
那么,在實(shí)例化DispatcherServlet的時(shí)候也是嚴(yán)格按照這個(gè)順序來執(zhí)行的。我們按照這個(gè)思路來看看在不同階段都做了些什么。
DispatcherServlet的注冊(cè)過程
通過查看源碼我們可以看到,在DispatcherServlet中存在大量的靜態(tài)屬性和一個(gè)靜態(tài)代碼塊,他的父類FrameworkServlet中存在少量靜態(tài)屬性。這些屬性大多都是用來命名的常量,我們需要關(guān)注一下這個(gè)靜態(tài)代碼塊。

通過上圖可以看到,在這個(gè)靜態(tài)代碼塊中初始化了DispatcherServlet的默認(rèn)策略,這些策略在后面的onRefresh方法中會(huì)用到。
結(jié)束靜態(tài)資源的初始化之后最后會(huì)調(diào)用構(gòu)造方法,在構(gòu)造方法中會(huì)將傳進(jìn)來的ApplicationContext賦值給FrameworkServlet的webApplicationContext屬性。隨后會(huì)配置Servlet的名稱及URLMapping。
至此,DispatcherServlet就已經(jīng)被注冊(cè)到Servlet容器中去了,隨后在容器啟動(dòng)時(shí)會(huì)調(diào)用GenericServlet的init方法。這個(gè)在我們學(xué)習(xí)Servlet時(shí)應(yīng)該已經(jīng)學(xué)習(xí)過了。Spring通過重寫init方法來真正啟動(dòng)SpringMVC模塊。讓我們來關(guān)注init過程。
HttpServletBean關(guān)鍵代碼

通過上述代碼可以看到,Spring會(huì)先讀取Servlet配置的參數(shù),即Servlet的InitParam。如果沒有配置則不做操作,緊接著初始化ServletBean,但這是個(gè)抽象方法,具體實(shí)現(xiàn)在FrameworkServlet中,我們進(jìn)入子類中查看相關(guān)代碼
FrameworkServlet關(guān)鍵代碼
/**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 注意,此前我們?cè)谧?cè)DispatcherServlet時(shí)手動(dòng)創(chuàng)建了一個(gè)ApplicationContext并將它賦值給了webApplicationContext,因此這里不為空,若采用舊的web.xml方式注冊(cè)DispatcherServlet則這里為空
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 如果是傳統(tǒng)方式會(huì)在這里創(chuàng)建ApplicatinContext
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.
synchronized (this.onRefreshMonitor) {
// 這里會(huì)調(diào)用DispatcherServlet的onRefresh方法,執(zhí)行初始化操作
onRefresh(wac);
}
}
return wac;
}
onRefresh方法關(guān)鍵部分
onRefresh方法會(huì)利用之前提到的默認(rèn)策略(沒有自定義策略的前提下)來初始化相關(guān)功能,關(guān)鍵代碼如下:
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 初始化文件上傳解析器
initMultipartResolver(context);
// 初始化國際化解析器
initLocaleResolver(context);
// 初始化主題解析器
initThemeResolver(context);
// 初始化HandlerMapping,默認(rèn)是RequestMappgingHandlerMapping和SimpleUrlHandlerMapping,其中RequestMappingHandlerMapping是用來處理@RequestMapping的,而SimpleUrlHandlerMapping是用來維護(hù)Uri路徑模式的顯示注冊(cè)
initHandlerMappings(context);
// 注冊(cè)HandlerAdapter,默認(rèn)是RequestMappingHandlerAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter這三種
initHandlerAdapters(context);
// 初始化異常處理器
initHandlerExceptionResolvers(context);
// 初始化視圖名稱轉(zhuǎn)換器
initRequestToViewNameTranslator(context);
// 初始化視圖解析器
initViewResolvers(context);
// 初始化重定向?qū)傩怨芾砥? initFlashMapManager(context);
}
至此我們已經(jīng)可以了解了Spring MVC在啟動(dòng)的大概流程,大致了解Spring MVC都做了什么。后續(xù)會(huì)繼續(xù)分析請(qǐng)求到達(dá)Dispatcher Servlet后是如何找到對(duì)應(yīng)的controller以及參數(shù)封裝,最后再到數(shù)據(jù)返回等流程的詳細(xì)過程。
由于本人能力有限,難免會(huì)有表述不清或錯(cuò)誤的地方,還希望各位不吝指教,大家共同學(xué)習(xí),一起進(jìn)步。