一、前言
前面的几章中学习了Spring IOC容器和AOP的实现,对这两个核心功能有了一定了解后,让我们一起学习一下IOC容器在常用的Web环境中是如何使用的,这也是Spring框架在Web环境中的重要应用场景。
回忆一下在Web项目中配置Spring的过程,首先,我们需要在web.xml中进行如下配置:
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring.xml</param-value>
</context-param>
这里的ContextLoaderListener就是在Web应用中启动IOC容器的入口,下面从源码的角度看一看ContextLoaderListener的实现。
二、源码学习
首先,看一下ContextLoaderListener的类继承关系,如下:
从图中可以看到,ContextLoaderListener实现了ServletContextListener接口,这个接口是Servlet API提供的用于监听Web应用生命周期的接口。这个接口中定义了Web应用启动和销毁的回调方法,代码如下:
public interface ServletContextListener extends EventListener {
/*
* Web应用启动后调用该方法
*/
void contextInitialized(ServletContextEvent event);
/*
* Web应用销毁后调用该方法
*/
void contextDestroyed(ServletContextEvent event);
}
可以看到ServletContextListener接口中定义了两个方法,分别在Web应用启动和销毁后调用,这样就可以通过这个接口来实现对Web应用生命周期的监听了。
ContextLoaderListener实现了ServletContextListener接口,代码如下:
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());
}
}
ContextLoaderListener中的代码比较简单,主要就是在Web应用启动和销毁时对IOC容器进行相应的处理,处理方法定义在其父类ContextLoader中。
首先,在Web应用启动后,创建和初始化应用上下文,代码如下:
public class ContextLoader {
/*
* 初始化根应用上下文
*/
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 {
if (this.context == null) {
// 创建一个应用上下文
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
// 应用上下文(IOC容器)还未被刷新,refresh是IOC容器初始化的入口
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
// 加载父容器,这里的loadParentContext是一个模板方法,默认返回null
// 目的是留给子类实现
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
// 配置并刷新应用上下文
// 这里会找到web.xml中配置的contextConfigLocation属性,作为应用上下文的配置路径
// 最后会调用ApplicationContext的refresh()方法初始化应用上下文
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;
}
}
}
经过以上代码的处理,就在Web应用环境中创建了一个根应用上下文,调用了refresh()方法对应用上下文进行了初始化,并将该应用上下文缓存在ServletContext中。
其次,在Web应用销毁后,关闭应用上下文,销毁ServletContext中的Spring框架属性,代码如下:
public class ContextLoader {
/*
* 关闭应用上下文
*/
public void closeWebApplicationContext(ServletContext servletContext) {
servletContext.log("Closing Spring root WebApplicationContext");
try {
if (this.context instanceof ConfigurableWebApplicationContext) {
// 关闭IOC容器
((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);
}
}
}
public class ContextCleanupListener implements ServletContextListener {
static void cleanupAttributes(ServletContext sc) {
// 获取ServletContext中的属性名集
Enumeration<String> attrNames = sc.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = attrNames.nextElement();
if (attrName.startsWith("org.springframework.")) {
Object attrValue = sc.getAttribute(attrName);
// 销毁相应的Bean
if (attrValue instanceof DisposableBean) {
try {
((DisposableBean) attrValue).destroy();
}
catch (Throwable ex) {
logger.error("Couldn't invoke destroy method of attribute with name '" + attrName + "'", ex);
}
}
}
}
}
}
到这里,我们对IOC容器在Web环境中的启动和关闭就有了一定的了解了,这个过程虽然不复杂,但是需要依托对IOC容器初始化过程的了解,也就是refresh()触发的IOC容器初始化过程,参考链接:IOC容器的初始化