容器的初始化之 Root WebApplicationContext 容器

首先,我們來回顧一下Tomcat啓動context容器的過程。

調用的是StandardContext.startInternal()方法,其中有一段邏輯是初始化Servlet相關的Listener:

在完成 Listener 實例化之後,tomcat 容器便啓動 OK 了。此時,tomcat 需要通知應用程序定義的 ServletContextListener,

方便應用程序完成自己的初始化邏輯,它會遍歷 ServletContextListener 實例,並調用其 contextInitialized 方法,

比如 spring 的 ContextLoaderListener。

這裏,ContextLoaderListener便是Root WebApplicationContext  的初始化和關閉的入口:

實現 ServletContextListener 接口,繼承 ContextLoader 類,實現 Servlet 容器啓動和關閉時,分別初始化和銷燬 WebApplicationContext 容器。

先來看ContextLoaderListenercontextInitialized()方法:

調用的是父類ContextLoader的initWebApplicationContext()方法:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		// <1> 若已經存在 ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 對應的 WebApplicationContext 對象,則拋出 IllegalStateException 異常。
		// 例如,在 web.xml 中存在多個 ContextLoader 。
		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
			throw new IllegalStateException(
					"Cannot initialize context because there is already a root application context present - " +
					"check whether you have multiple ContextLoader* definitions in your web.xml!");
		}

		// <2> 打印日誌
		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
		if (logger.isInfoEnabled()) {
			logger.info("Root WebApplicationContext: initialization started");
		}

		// 記錄開始時間
		long startTime = System.currentTimeMillis();

		try {
			// Store context in local instance variable, to guarantee that
			// it is available on ServletContext shutdown.
			if (this.context == null) {
				// <3> 初始化 context ,即創建 context 對象
				this.context = createWebApplicationContext(servletContext);
			}

			// <4> 如果是 ConfigurableWebApplicationContext 的子類,如果未刷新,則進行配置和刷新
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				// <4.1> 未刷新(激活)
				if (!cwac.isActive()) {
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					// <4.2> 無父容器,則進行加載和設置。
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					// <4.3> 配置 context 對象,並進行刷新
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			// <5> 記錄在 servletContext 中,跟<1>中的校驗相對應
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

			// <6> 記錄到 currentContext 或 currentContextPerThread 中
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			else if (ccl != null) {
				currentContextPerThread.put(ccl, this.context);
			}

			// <7> 打印日誌
			if (logger.isInfoEnabled()) {
				long elapsedTime = System.currentTimeMillis() - startTime;
				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
			}

			// <8> 返回 context
			return this.context;
		}
		catch (RuntimeException | Error ex) {
			logger.error("Context initialization failed", ex);
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
			throw ex;
		}
	}

標紅的方法1:

初始化context,即創建 WebApplicationContext 對象

看下determineContextClass方法:

  1. 分成兩種情況。前者,從 ServletContext 獲取配置的 context 類;後者,從 ContextLoader.properties 獲取配置的 context 類。
  2. 默認情況下,我們不會主動在 ServletContext 中配置 context 類,所以基本是使用 ContextLoader.properties 配置的 context 類,即 XmlWebApplicationContext 類。

下面是從 ContextLoader.properties 獲取配置的 context 類的邏輯:

標紅的方法2:

配置 ConfigurableWebApplicationContext 對象,並進行刷新

  1. 此處,註釋上既寫了 wac ,又寫了 context ,實際上,是等價的東西。下面的文字,我們統一用 wac 
  2. <1> 處,如果 wac 使用了默認編號,則重新設置 id 屬性。默認情況下,我們不會對 wac 設置編號,所以會執行進去。而實際上,id 的生成規則,也分成使用 contextId 在 <context-param /> 標籤中設置,和自動生成兩種情況。?? 默認情況下,會走第二種情況
  3. 關鍵<3> 處,設置 context 的配置文件地址。後面IOC容器初始化要用到。

  1. 【關鍵】<6> 處, 刷新 wac ,執行初始化。此處,就會進行一些的 Spring 容器的初始化。這個IOC源碼解析的時候已經學習過了,就不多說了。

最後我們來看下Root WebApplicationContext 容器的關閉,ContextLoaderListenercontextDestroyed()方法:

以上就是Root WebApplicationContext 容器的初始化和關閉的源碼分析。

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