Spring源码学习【七】Web环境中启动和关闭IOC容器

一、前言

前面的几章中学习了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容器的初始化 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值