容器的初始化之 Servlet WebApplicationContext 容器

在開始之前,我們還是回過頭看一眼 web.xml 的配置。代碼如下:

  1. 即, Servlet WebApplicationContext 容器的初始化,是在 DispatcherServlet 初始化的過程中執行。

HttpServletBean ,負責將 ServletConfig 設置到當前 Servlet 對象中。類上的簡單註釋如下:

FrameworkServlet ,負責初始化 Spring Servlet WebApplicationContext 容器。類上的簡單註釋如下:

DispatcherServlet ,負責初始化 Spring MVC 的各個組件,以及處理客戶端的請求。類上的簡單註釋如下:

每一層的 Servlet 實現類,執行對應負責的邏輯。

下面,我們逐個類來進行解析。

HttpServletBean

實現 EnvironmentCapable、EnvironmentAware 接口,繼承 HttpServlet 抽象類,負責將 ServletConfig 集成到 Spring 中。當然,HttpServletBean 自身也是一個抽象類。

1. 構造方法

environment 屬性,相關的方法,代碼如下:

爲什麼 environment 屬性,能夠被自動注入呢?答案是 實現了EnvironmentAware 接口。

requiredProperties 屬性,必須配置的屬性的集合。可通過 #addRequiredProperty(String property) 方法,添加到其中。代碼如下:

2. init

#init() 方法,負責將 ServletConfig 設置到當前 Servlet 對象中。代碼如下:

<1> 處,解析 Servlet 配置的 <init-param /> 標籤,封裝到 PropertyValues pvs 中。其中,ServletConfigPropertyValues 是 HttpServletBean 的私有靜態類,繼承 MutablePropertyValues 類,ServletConfig 的 PropertyValues 封裝實現類。代碼如下:

代碼簡單,實現兩方面的邏輯:<1> 處,遍歷 ServletConfig 的初始化參數集合,添加到 ServletConfigPropertyValues 中;<2> 處,判斷要求的屬性是否齊全。如果不齊全,則拋出 ServletException 異常。

  1. <2.1> 處,將當前的這個 Servlet 對象,轉化成一個 BeanWrapper 對象。從而能夠以 Spring 的方式來將 pvs 注入到該 BeanWrapper 對象中。簡單來說,BeanWrapper 是 Spring 提供的一個用來操作 Java Bean 屬性的工具,使用它可以直接修改一個對象的屬性
  2. <2.2> 處,註冊自定義屬性編輯器,一旦碰到 Resource 類型的屬性,將會使用 ResourceEditor 進行解析。
  3. <2.3> 處,空實現,留給子類覆蓋。代碼如下:

然而,目前子類並沒有任何實現。

  1. <2.4> 處,以 Spring 的方式來將 pvs 注入到該 BeanWrapper 對象中,即設置到當前 Servlet 對象中。可能比較費解,我們還是舉個例子。假設如下:

此處有配置了 contextConfigLocation 屬性,那麼通過 <2.4> 處的邏輯,會反射設置到 FrameworkServlet.contextConfigLocation 屬性。代碼如下:

<3> 處,調用 #initServletBean() 方法,子類來實現,實現自定義的初始化邏輯。目前,FrameworkServlet 實現類該方法。代碼如下:

FrameworkServlet

實現 ApplicationContextAware 接口,繼承 HttpServletBean 抽象類,負責初始化 Spring Servlet WebApplicationContext 容器。同時,FrameworkServlet 自身也是一個抽象類。

1. 構造方法

FrameworkServlet 的屬性還是非常多,我們還是隻看部分的關鍵屬性。代碼如下:

其中,contextClass 屬性,創建的 WebApplicationContext 類型,默認爲 DEFAULT_CONTEXT_CLASS 。代碼如下:

又是我們熟悉的 XmlWebApplicationContext 類。在上一篇文章的 ContextLoader.properties 配置文件中,我們已經看到咯

  1. contextConfigLocation 屬性,配置文件的地址。例如:/WEB-INF/spring-servlet.xml 
  2. webApplicationContext 屬性,WebApplicationContext 對象,即本文的關鍵,Servlet WebApplicationContext 容器。它有四種方式進行“創建”。

方式一:通過構造方法,代碼如下:

通過方法參數 webApplicationContext 

方式二:因爲實現 ApplicationContextAware 接口,也可以 Spring 注入。代碼如下:

方式三:見 #findWebApplicationContext() 方法。

方式四:見 #createWebApplicationContext(WebApplicationContext parent) 方法。

2. initServletBean

#initServletBean() 方法,進一步初始化當前 Servlet 對象。實際上,重心在初始化 Servlet WebApplicationContext 容器。代碼如下:

3. initWebApplicationContext

#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 對象。代碼如下:

  1. 一般情況下,我們不會配置 contextAttribute 屬性。所以,這段邏輯暫時無視。

========== 第三種情況 ==========

這種情況,就是我們在 「4.1 構造方法」 提到的 Servlet WebApplicationContext 容器的第四種方式。

如果此處 wac 還是爲空,則調用 #createWebApplicationContext(WebApplicationContext parent) 方法,創建一個 WebApplicationContext 對象。代碼如下:

    1. <a> 處,獲得 context 的類,即 contextClass 屬性。並且,如果非 ConfigurableWebApplicationContext 類型,拋出 ApplicationContextException 異常。
    2. <b> 處,創建 context 類的對象。
    3. <c> 處,設置 environmentparentconfigLocation 屬性。其中,configLocation 是個重要屬性。
    4. <d> 處,調用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 

詳細解析,見 4. configureAndRefreshWebApplicationContext

========== END ==========

<3> 處,如果未觸發刷新事件,則調用 #onRefresh(ApplicationContext context) 主動觸發刷新事件。詳細解析,見 5. onRefresh 中。

<4> 處,如果 publishContext 爲 true 時,則將 context 設置到 ServletContext 中。

4. configureAndRefreshWebApplicationContext

#configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,配置和初始化 wac 。代碼如下:

  1. 實際上,大體邏輯上,和 Root WebApplicationContext 容器 的 ContextLoader#configureAndRefreshWebApplicationContext 方法是一致的。
  2. 【相同】<1> 處,如果 wac 使用了默認編號,則重新設置 id 屬性。
  3. 【類似】<2> 處,設置 wac 的 servletContextservletConfignamespace 屬性。
  4. 【獨有】<3> 處,添加監聽器 SourceFilteringListener 到 wac 中。這塊的詳細解析,見  5. onRefresh 中。
  5. 【相同】<4> 處,初始化屬性資源
  6. 【獨有】<5> 處,執行處理完 WebApplicationContext 後的邏輯。目前是個空方法,暫無任何實現。
  7. 【相同】<6> 處,執行自定義初始化 context 。
  8. 【相同】<7> 處,刷新 wac ,從而初始化 wac 

5. onRefresh

#onRefresh(ApplicationContext context) 方法,當 Servlet WebApplicationContext 刷新完成後,觸發 Spring MVC 組件的初始化。代碼如下:

這是一個空方法,具體的實現,在子類 DispatcherServlet 中。代碼如下:

  1. 這裏,我們先不深究,在 DispatcherServlet 的初始化過程中,詳細解析。

#onRefresh() 方法,有兩種方式被觸發:

  1. 方式一,在  3. initWebApplicationContext  中,有兩種情形,會觸發。
    1. 情形一:情況一 + wac 已激活。
    2. 情形二:情況二。
    3. 這兩種情形,此時 refreshEventReceived 爲 false ,所以會順着 #initWebApplicationContext() 方法的 <3> 的邏輯,調用 #onRefresh() 方法。?? 貌似說的有點繞,大家自己順順
  2. 方式二,在  3. initWebApplicationContext  中,也有兩種情況,會觸發。不過相比方式一來說,過程會“曲折”一點。
    1. 情形一:情況一 + wac 未激活。
    2. 情形二:情況三。
    3. 這兩種情形,都會調用 #configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) 方法,在 wac 執行刷新 wac.refresh() 完成後,會回調在該方法中註冊的 SourceFilteringListener 監聽器。如下:

詳細解析,見  5. SourceFilteringListener

6. SourceFilteringListener

SourceFilteringListener實現了ApplicationListener接口,重寫了onApplicationEvent方法,關鍵就是這個方法。

調用的是delegate.onApplicationEvent(event); 這裏的delegate指的其實就是構造函數傳進來的ContextRefreshListener。

所以最終調用的是org.springframework.web.servlet.FrameworkServlet.ContextRefreshListener#onApplicationEvent()。

這裏,把refreshEventReceived設置成了true,回到了 5. onRefresh

以上,就是Servlet WebApplicationContext 容器啓動過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章