在開始之前,我們還是回過頭看一眼 web.xml 的配置。代碼如下:
HttpServletBean ,負責將 ServletConfig 設置到當前 Servlet 對象中。類上的簡單註釋如下:
FrameworkServlet ,負責初始化 Spring Servlet WebApplicationContext 容器。類上的簡單註釋如下:
DispatcherServlet ,負責初始化 Spring MVC 的各個組件,以及處理客戶端的請求。類上的簡單註釋如下:
實現 EnvironmentCapable、EnvironmentAware 接口,繼承 HttpServlet 抽象類,負責將 ServletConfig 集成到 Spring 中。當然,HttpServletBean 自身也是一個抽象類。
爲什麼 environment 屬性,能夠被自動注入呢?答案是 實現了EnvironmentAware 接口。
requiredProperties 屬性,必須配置的屬性的集合。可通過 #addRequiredProperty(String property) 方法,添加到其中。代碼如下:
#init() 方法,負責將 ServletConfig 設置到當前 Servlet 對象中。代碼如下:
<1> 處,解析 Servlet 配置的 <init-param /> 標籤,封裝到 PropertyValues pvs 中。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有靜態類,繼承 MutablePropertyValues 類,ServletConfig 的 PropertyValues 封裝實現類。代碼如下:
代碼簡單,實現兩方面的邏輯:<1> 處,遍歷 ServletConfig 的初始化參數集合,添加到 ServletConfigPropertyValues 中;<2> 處,判斷要求的屬性是否齊全。如果不齊全,則拋出 ServletException 異常。
- <2.1> 處,將當前的這個 Servlet 對象,轉化成一個 BeanWrapper 對象。從而能夠以 Spring 的方式來將 pvs 注入到該 BeanWrapper 對象中。簡單來說,BeanWrapper 是 Spring 提供的一個用來操作 Java Bean 屬性的工具,使用它可以直接修改一個對象的屬性。
- <2.2> 處,註冊自定義屬性編輯器,一旦碰到 Resource 類型的屬性,將會使用 ResourceEditor 進行解析。
- <2.3> 處,空實現,留給子類覆蓋。代碼如下:
此處有配置了 contextConfigLocation 屬性,那麼通過 <2.4> 處的邏輯,會反射設置到 FrameworkServlet.contextConfigLocation 屬性。代碼如下:
<3> 處,調用 #initServletBean() 方法,子類來實現,實現自定義的初始化邏輯。目前,FrameworkServlet 實現類該方法。代碼如下:
實現 ApplicationContextAware 接口,繼承 HttpServletBean 抽象類,負責初始化 Spring Servlet WebApplicationContext 容器。同時,FrameworkServlet 自身也是一個抽象類。
FrameworkServlet 的屬性還是非常多,我們還是隻看部分的關鍵屬性。代碼如下:
其中,contextClass 屬性,創建的 WebApplicationContext 類型,默認爲 DEFAULT_CONTEXT_CLASS 。代碼如下:
又是我們熟悉的 XmlWebApplicationContext 類。在上一篇文章的 ContextLoader.properties 配置文件中,我們已經看到咯。
- contextConfigLocation 屬性,配置文件的地址。例如:/WEB-INF/spring-servlet.xml 。
- webApplicationContext 屬性,WebApplicationContext 對象,即本文的關鍵,Servlet WebApplicationContext 容器。它有四種方式進行“創建”。
通過方法參數 webApplicationContext 。
方式二:因爲實現 ApplicationContextAware 接口,也可以 Spring 注入。代碼如下:
方式三:見 #findWebApplicationContext() 方法。
方式四:見 #createWebApplicationContext(WebApplicationContext parent) 方法。
#initServletBean() 方法,進一步初始化當前 Servlet 對象。實際上,重心在初始化 Servlet WebApplicationContext 容器。代碼如下:
#initWebApplicationContext() 方法,初始化 Servlet WebApplicationContext 對象。代碼如下:
protected WebApplicationContext initWebApplicationContext() {
// <1> 獲得根 WebApplicationContext 對象
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// <2> 獲得 WebApplicationContext wac 變量
WebApplicationContext wac = null;
// 第一種情況,如果構造方法已經傳入 webApplicationContext 屬性,則直接使用
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
// 如果是 ConfigurableWebApplicationContext 類型,並且未激活,則進行初始化
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
// 設置 wac 的父 context 爲 rootContext 對象
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);
}
// 配置和初始化 wac
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 第二種情況,從 ServletContext 獲取對應的 WebApplicationContext 對象
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();
}
// 第三種,創建一個 WebApplicationContext 對象
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
// <3> 如果未觸發刷新事件,則主動觸發刷新事件
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) {
onRefresh(wac);
}
}
// <4> 將 context 設置到 ServletContext 中
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
<1> 處,調用 WebApplicationContextUtils#getWebApplicationContext((ServletContext sc) 方法,獲得 Root WebApplicationContext 對象,
這就是在 Root WebApplicationContext 容器中初始化的呀。代碼如下:
而這個是在Root WebApplicationContext 容器初始化的時候進行設置的:
org.springframework.web.context.ContextLoader#initWebApplicationContext
<2> 處,獲得 WebApplicationContext wac 變量。下面,會分成三種情況。
如果構造方法已經傳入 webApplicationContext 屬性,則直接使用。實際上,就是我們在 1. 構造方法 提到的 Servlet WebApplicationContext 容器的第一、二種方式。
實際上,這塊代碼和 ContextLoader#initWebApplicationContext(ServletContext servletContext) 的中間段是一樣的。除了 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 的具體實現代碼不同。詳細解析,見 4. configureAndRefreshWebApplicationContext 。
這種情況,就是我們在 4.1 構造方法 提到的 Servlet WebApplicationContext 容器的第三種方式。
如果此處 wac 還是爲空,則調用 #findWebApplicationContext() 方法,從 ServletContext 獲取對應的 WebApplicationContext 對象。代碼如下:
這種情況,就是我們在 「4.1 構造方法」 提到的 Servlet WebApplicationContext 容器的第四種方式。
如果此處 wac 還是爲空,則調用 #createWebApplicationContext(WebApplicationContext parent) 方法,創建一個 WebApplicationContext 對象。代碼如下:
-
- <a> 處,獲得 context 的類,即 contextClass 屬性。並且,如果非 ConfigurableWebApplicationContext 類型,拋出 ApplicationContextException 異常。
- <b> 處,創建 context 類的對象。
- <c> 處,設置 environment、parent、configLocation 屬性。其中,configLocation 是個重要屬性。
- <d> 處,調用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。
詳細解析,見 4. configureAndRefreshWebApplicationContext。
<3> 處,如果未觸發刷新事件,則調用 #onRefresh(ApplicationContext context) 主動觸發刷新事件。詳細解析,見 5. onRefresh 中。
<4> 處,如果 publishContext 爲 true 時,則將 context 設置到 ServletContext 中。
4. configureAndRefreshWebApplicationContext
#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。代碼如下:
- 實際上,大體邏輯上,和 Root WebApplicationContext 容器 的 ContextLoader#configureAndRefreshWebApplicationContext 方法是一致的。
- 【相同】<1> 處,如果 wac 使用了默認編號,則重新設置 id 屬性。
- 【類似】<2> 處,設置 wac 的 servletContext、servletConfig、namespace 屬性。
- 【獨有】<3> 處,添加監聽器 SourceFilteringListener 到 wac 中。這塊的詳細解析,見 5. onRefresh 中。
- 【相同】<4> 處,初始化屬性資源。
- 【獨有】<5> 處,執行處理完 WebApplicationContext 後的邏輯。目前是個空方法,暫無任何實現。
- 【相同】<6> 處,執行自定義初始化 context 。
- 【相同】<7> 處,刷新 wac ,從而初始化 wac 。
#onRefresh(ApplicationContext context) 方法,當 Servlet WebApplicationContext 刷新完成後,觸發 Spring MVC 組件的初始化。代碼如下:
這是一個空方法,具體的實現,在子類 DispatcherServlet 中。代碼如下:
- 方式一,在 3. initWebApplicationContext 中,有兩種情形,會觸發。
- 方式二,在 3. initWebApplicationContext 中,也有兩種情況,會觸發。不過相比方式一來說,過程會“曲折”一點。
詳細解析,見 5. SourceFilteringListener 。
SourceFilteringListener實現了ApplicationListener接口,重寫了onApplicationEvent方法,關鍵就是這個方法。
調用的是delegate.onApplicationEvent(event); 這裏的delegate指的其實就是構造函數傳進來的ContextRefreshListener。
所以最終調用的是org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent()。
這裏,把refreshEventReceived設置成了true,回到了 5. onRefresh 。
以上,就是Servlet WebApplicationContext 容器啓動過程。