(一)背景知识
Web容器中有一个全局上下文,即ServletContext,为Spring容器提供宿主环境。ServletContext中保存的属性(attribute)和参数(parameter)属于整个应用,被所有servlet所共享。ServletContext中保存了两种不同类型的Spring容器上下文,一种是根上下文,主要用于管理整个Web应用程序所需要使用到的一些可共享的组件,如DAO,数据库的ConnectionFactory等待。另外一种是在web.xml中所配置的Servlet,每个Servlet都会创建一个自己的Spring容器上下文,根上下文是所有其他上下文的父上下文。
根上下文的创建以及初始化是通过ContextLoaderListener来实现的,ContextLoaderListener的UML类图如下,ContextLoaderListener继承了ContextLoader类,并实现了ServletContextListener接口。
(二)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);
}
}
}
}
}
}