【Spring】- ContextLoaderListener 工作原理
ContextLoaderListener:上下文載入器監聽器
作用:負責IOC容器的關閉\開啟工作
ContextLoaderListener 原始碼:
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{ public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); } }
web.xml 配置:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:config/applicationContext.xml </param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
工作原理: ContextLoaderListener 實現ServletContextListener介面,ServletContextListener作為ServeltContext的監聽器,當Servlet容器啟動的時候,Servlet容器會根據Context容器生成ServletContext物件並進行初始化,然後呼叫ServletContextListener進行事件監聽,因此ContextLoaderLister在Servlet容器例項化時會進行無參構造器的形式例項化,然後呼叫ServletContextListener的contextInitialized(ServletContextEvent event)方法
擴充套件:ServletContext物件的生成程式碼: Tomcate內部的StandardContext類(推薦書籍:How Tomcat Work)
@Override
public ServletContext getServletContext() {
if (context == null) {
context = new ApplicationContext(this);
if (altDDName != null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
return (context.getFacade());
}
IOC容器的初始化流程: contextInitialized(ServletContextEvent event)方法分析:
ContextLoaderListener的IOC容器初始化工作是交給其父類ContextLoader實際處理的
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//根據ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE屬性判斷上下文是否已經啟動
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!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
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) { //判斷上下文是否為空,如果為空則建立webApplicationContext
this.context = createWebApplicationContext(servletContext);
}
//判斷是否是ConfigurableWebApplicationContext型別的上下文,如果是進行相關的上下文的初始化配置
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) { //判斷上下文是否啟用:refresh方法
//設定父類上下文
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//webApplicationContext的初始化配置
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
主要步驟:
- 建立上下文:ContextLoader的createWebApplicationContext(ServletContext sc)方法處理,值得一提的是Spring支援自定義的上下文的功能,主要通過在web.xml檔案中配置contextClass的形式提供,規定自定義實現的上下文必須實現ConfigurableWebApplicationContext介面,主要是方便WebApplicationContext的配置
web.xml配置自定義上下文方法: 補充:context-param:在Servlet容器啟動之後會被封裝進ServletContext物件中,引數值可以通過servletContext.getInitParameter("引數名")的形式獲取
<context-param>
<param-name>contextClass</param-name>
<param-value>
自定義的上下文類的全路徑
</param-value>
</context-param>
判斷建立的上下文的方法:
protected Class<?> determineContextClass(ServletContext servletContext) {
String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
if (contextClassName != null) { //自定義的webApplicationContext
try {
return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load custom context class [" + contextClassName + "]", ex);
}
//預設的webApplicationContext:XmlWebApplicationContext
contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
try {
return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new ApplicationContextException(
"Failed to load default context class [" + contextClassName + "]", ex);
}
}
}
工作流程:首先獲取ServletContext配置的contextClass初始化引數,如果存在則認定為客戶使用自定義的上下文,然後使用類載入器載入,如果客戶未自定義上下文則使用預設的webApplicationContext:預設記錄檔案:ContextLoader.properties
org.springframework.web.context.WebApplicationContext=
org.springframework.web.context.support.XmlWebApplicationContext
明顯Spring預設的webApplicationContext為XmlWebApplicationContext型別
- 配置上下文
如果實現了ConfigurableWebApplicationContext介面,則呼叫介面的功能進行IOC容器的例項化工作:例如webAplicationContext的唯一標識(判斷IOC容器是否啟動),設定父上下文等
- 將執行緒和應用上下文繫結
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
流程: ServletContext首先存放IOC容器已經初始化的標識,然後通過比較執行緒上下文的類載入器和類本身的類載入器,判斷是否處於同一個執行緒,如果不是則繫結執行緒和上下文物件(通過繫結執行緒類載入器形式繫結),繫結關係維護在currentContextPerThread的Map中,經過上述步驟就完成了IOC容器的所有準備工作,可以提供IOC容器的服務
IOC容器關閉過程:
@Override
public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}
從程式碼可以看出IOC容器的關閉經過兩個步驟,
- 關閉webApplicationContext
- 清理webApplicationContext的相關資源
關閉容器:ContextLoader的closeWebApplicationContext(ServletContext servletContext)方法
public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
if (this.context instanceof ConfigurableWebApplicationContext) {
((ConfigurableWebApplicationContext) this.context).close();
}
}
finally {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = null;
}else if (ccl != null) {
currentContextPerThread.remove(ccl);
}
servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (this.parentContextRef != null) {
this.parentContextRef.release();
}
}
}
流程:按照標準的IOC流程關閉本身及關聯的IOC容器,將所有引用應用上下文的物件置空,ServletContext應用清除上下文啟動的標識
清理IOC相關資源:
static void cleanupAttributes(ServletContext sc) {
Enumeration<String> attrNames = sc.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = attrNames.nextElement();
if (attrName.startsWith("org.springframework.")) {
Object attrValue = sc.getAttribute(attrName);
if (attrValue instanceof DisposableBean) {
try {
((DisposableBean) attrValue).destroy();
}
catch (Throwable ex) {
logger.error("Couldn't invoke destroy method of attribute with name '" + attrName + "'", ex);
}
}
}
}
}
流程:主要是通過查詢DisposableBean介面的Bean,呼叫其destroy()方法實現使用者自定義的Bean銷燬的功能,例如Bean銷燬時需要進行某些處理,可以通過實現DisposableBean介