首先,我們來回顧一下Tomcat啓動context容器的過程。
調用的是StandardContext.startInternal()方法,其中有一段邏輯是初始化Servlet相關的Listener:
在完成 Listener 實例化之後,tomcat 容器便啓動 OK 了。此時,tomcat 需要通知應用程序定義的 ServletContextListener,
方便應用程序完成自己的初始化邏輯,它會遍歷 ServletContextListener 實例,並調用其 contextInitialized 方法,
比如 spring 的 ContextLoaderListener。
這裏,ContextLoaderListener便是Root WebApplicationContext 的初始化和關閉的入口:
實現 ServletContextListener 接口,繼承 ContextLoader 類,實現 Servlet 容器啓動和關閉時,分別初始化和銷燬 WebApplicationContext 容器。
先來看ContextLoaderListener的contextInitialized()方法:
調用的是父類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;
}
}
初始化context,即創建 WebApplicationContext 對象
- 分成兩種情況。前者,從 ServletContext 獲取配置的 context 類;後者,從 ContextLoader.properties 獲取配置的 context 類。
- 默認情況下,我們不會主動在 ServletContext 中配置 context 類,所以基本是使用 ContextLoader.properties 配置的 context 類,即 XmlWebApplicationContext 類。
下面是從 ContextLoader.properties 獲取配置的 context 類的邏輯:
配置 ConfigurableWebApplicationContext 對象,並進行刷新
- 此處,註釋上既寫了 wac ,又寫了 context ,實際上,是等價的東西。下面的文字,我們統一用 wac 。
- <1> 處,如果 wac 使用了默認編號,則重新設置 id 屬性。默認情況下,我們不會對 wac 設置編號,所以會執行進去。而實際上,id 的生成規則,也分成使用 contextId 在 <context-param /> 標籤中設置,和自動生成兩種情況。?? 默認情況下,會走第二種情況。
- 【關鍵】<3> 處,設置 context 的配置文件地址。後面IOC容器初始化要用到。
最後我們來看下Root WebApplicationContext 容器的關閉,ContextLoaderListener的contextDestroyed()方法: