【Spring】- ContextLoaderListener 工作原理

旧巷老猫 提交于 2019-11-30 06:36:13

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;
    }
}

主要步骤:

  1. 创建上下文: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类型

  1. 配置上下文

如果实现了ConfigurableWebApplicationContext接口,则调用接口的功能进行IOC容器的实例化工作:例如webAplicationContext的唯一标识(判断IOC容器是否启动),设置父上下文等

  1. 将线程和应用上下文绑定
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容器的关闭经过两个步骤,

  1. 关闭webApplicationContext
  2. 清理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接口来实现该功能

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!