通过ContextLoaderListener加载Spring容器的根上下文以及根上下文的作用

(一)背景知识      

        Web容器中有一个全局上下文,即ServletContext,为Spring容器提供宿主环境。ServletContext中保存的属性(attribute)和参数(parameter)属于整个应用,被所有servlet所共享。ServletContext中保存了两种不同类型的Spring容器上下文,一种是根上下文,主要用于管理整个Web应用程序所需要使用到的一些可共享的组件,如DAO,数据库的ConnectionFactory等待。另外一种是在web.xml中所配置的Servlet,每个Servlet都会创建一个自己的Spring容器上下文,根上下文是所有其他上下文的父上下文。

      根上下文的创建以及初始化是通过ContextLoaderListener来实现的,ContextLoaderListener的UML类图如下,ContextLoaderListener继承了ContextLoader类,并实现了ServletContextListener接口。f1d3dfe772c72c3a2eefb7ad06f505d2425.jpg

 

(二)ServletContextListener接口和ContextLoaderListener类

      web容器启动和关闭的时候都会触发ServletContextEvent事件,ContextLoaderListener监听到ServletContextEvent事件之后会根据事件的状态分别调用其contextInitialized方法和contextDestroyed方法,进行web应用的根上下文的初始化或销毁工作。这里的根上下文指的就是spring的IOC容器的上下文。

      contextInitialized和contextDestroyed方法声明在ServletContextListener接口中,在ContextLoaderListener中实现。

ServletContextListener:

public interface ServletContextListener extends EventListener {
    //初始化Spring的IOC容器的上下文,此时,web应用中的所有filter和servlet还没有初始化
    public void contextInitialized ( ServletContextEvent sce );
    //销毁Spring的IOC容器的上下文,此时,所有的filter和servlet都已经被销毁
    public void contextDestroyed ( ServletContextEvent sce );
}

ContextLoaderListener:

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

        //默认构造方法
	public ContextLoaderListener() {
	}

        //带根上下文参数的构造方法,该构造方法可以在构造时将根上下文作为参数传入
	public ContextLoaderListener(WebApplicationContext context) {
		super(context);
	}

	//初始化web应用的根上下文,即Spring的IOC容器的上下文
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}

	//销毁web应用的根上下文,即Spring的IOC容器的上下文
	@Override
	public void contextDestroyed(ServletContextEvent event) {
		closeWebApplicationContext(event.getServletContext());
		ContextCleanupListener.cleanupAttributes(event.getServletContext());
	}
}

如果要使用ContextLoaderListener,直接在web.xml中进行配置:

<listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

 

(三)根上下文初始化过程

        contextInitialized方法直接调用继承于ContextLoader的initWebApplicationContext方法,来初始化web应用的根上下文。根上下文存储在servletContext中,其键为WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE。       

       contextInitialized方法中所调用的initWebApplicationContext(ServletContext servletContext)方法继承于ContextLoader类。

public class ContextLoader {
                //初始化web应用的根上下文的方法
		public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
               //判断是否已经有根上下文并已经配置到servletContext中了
		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 {
                       //子类ContextLoaderListener的带参数的构造函数会传入一个根上下文,此时this.context非null,而无参数的构造函数不会给this.context赋值。
			if (this.context == null) {
                                //创建根上下文
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
                                //判断是否刷新过
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						//设置根上下文的父上下文,这里的父上下文中保存了一些可以被多个web应用所共享的信息
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
                                       //刷新根上下文,包括上下文的id,ContextConfigLocation,ServletContext等等
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
          //将根上下文保存在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;
		}
	}
}

 

        生成根上下文的方法:createWebApplicationContext(ServletContext sc)

public class ContextLoader {
        //生成根上下文的方法
        protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
                //获取根上下文的类型
		Class<?> contextClass = determineContextClass(sc);
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException("Custom context class [" + contextClass.getName() +
					"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
		}
                //生成根上下文
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
}

        

       获取根上下文的类型的方法:determineContextClass

public class ContextLoader {
    protected Class<?> determineContextClass(ServletContext servletContext) {
               //从servletContext的初始化参数中读取根上下文的类型
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
                       //从配置文件中读取根上下文的类型
			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);
			}
		}
	}
}

     

       刷新根上下文的方法:configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc)

public class ContextLoader {
    protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			//设置根上下文的id
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}

                //设置servletContext的应用给根上下文
		wac.setServletContext(sc);
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
                        //设置根上下文的配置
			wac.setConfigLocation(configLocationParam);
		}

		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}

		customizeContext(sc, wac);
		wac.refresh();
	}
}

 

(四)根上下文销毁流程

       contextDestroyed方法直接调用ContextLoder中的closeWebApplicationContext(ServletContext servletContext)方法,来销毁保存在ServletContext中的根上下文,同时也会把根上下文的父上下文(如果有的话)移除。

public class ContextLoader {
	 	public void closeWebApplicationContext(ServletContext servletContext) {
		servletContext.log("Closing Spring root WebApplicationContext");
		try {
			if (this.context instanceof ConfigurableWebApplicationContext) {
                               //关闭根上下文,释放根上下文占有的所有资源和锁,销毁所有单例bean对象
				((ConfigurableWebApplicationContext) this.context).close();
			}
		}
		finally {
			ClassLoader ccl = Thread.curr entThread().getContextClassLoader();
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = null;
			}
			else if (ccl != null) {
				currentContextPerThread.remove(ccl);
			}
                        //从servletContext中移除根上下文
			servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
                        //移除根上下文的父上下文
			if (this.parentContextRef != null) {
				this.parentContextRef.release();
			}
		}
	}
}

       紧接着,contextDestroyed方法调用ContextCleanupListener.cleanupAttributes,将ServletContext中保存的(保存在attribute中)所有实现了DisposableBean接口的属性全部销毁。

public class ContextCleanupListener implements ServletContextListener {

	private static final Log logger = LogFactory.getLog(ContextCleanupListener.class);


	@Override
	public void contextInitialized(ServletContextEvent event) {
	}

	@Override
	public void contextDestroyed(ServletContextEvent event) {
		cleanupAttributes(event.getServletContext());
	}


	/**
	 * Find all ServletContext attributes which implement {@link DisposableBean}
	 * and destroy them, removing all affected ServletContext attributes eventually.
	 * @param sc the ServletContext to check
	 */
	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);
					}
				}
			}
		}
	}

}

 

转载于:https://my.oschina.net/u/3498791/blog/3065265

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值