《Spring系列一》-Spring与tomcat的不解之缘

在web后端开发中,Spring无疑是龙头的老大。那么Spring是如何与tomcat容器关联起来的,本篇文章将讲述Spring容器是如何绑定到tomcat中的。

ServletContextListener

该接口是Servlet包中的接口,在Tomcat启动时会执行该接口对象的contextInitialized(ServletContextEvent sce)方法,当Tomcat关闭时会调用该接口对象的contextDestroyed(ServletContextEvent sce)方法。

该接口相当于Tomcat启动和关闭事件的回调接口,如果想在Tomcat启动或者关闭做一些事情,那么就可以实现该接口并将实现该接口的类通过web.xml配置文件进行注册。

public interface ServletContextListener extends EventListener {

    public void contextInitialized(ServletContextEvent sce);

    public void contextDestroyed(ServletContextEvent sce);
}

ContextLoaderListener

Spring利用了ServletContextListener的该特性,自定义ContextLoaderListener类实现ServletContextListener。通过ContextLoaderListener,Spring可以轻松的与Tomcat联系起来。(当然你也可以自定义自己的Listener,实现自己的容器)

public class ContextLoaderListener extends ContextLoader implements ServletContextListener

除了定义类外,还需要在web的配置文件中将该类添加上,否则Tomcat启动时不会感知到Listener的存在。这就是为什么我们在构建Spring项目时在web.xml中配置ContextLoaderListener的原因,除此之外,还需要在web.xml中指定spring自定义的参数值,例如最常见contextConfigLocation参数,web.xml中的参数都会被tomcat解析,放到servletContext中,在ContextLoaderListener中spring会从servletContext中取出这些参数,并进行响应的操作。


    org.springframework.web.context.ContextLoaderListener
  

tomcat服务启动时,会调用ContextLoaderListener的contextInitialized()方法,该方法调用了initWebApplicationContext()方法,下面看一下initWebApplicationContext()方法的实现。
整个spring环境的启动,都在该方法中了,简单来说就做了两件事件:

  1. spring环境启动前的准备工作
  2. 构建spring环境-wac.refresh()方法(本文不讲)

前期的准备工作如要如下:

  1. 判断spring容器是否已经存在,如果存在,则抛出异常,因为一个tomcat中只能存在一个spring容器
  2. 如果context不存在,则创建WebApplicationContext(这里创建的都为ConfigurableWebApplicationContext)
  3. 如果context是ConfigurableWebApplicationContext,设置Parent,Parent一般为null(需要在web.xml中指定,一般不指定)
  4. 修改context的Id,改一个好是别的,默认该为(类名:servletContextPath)或者通过web.xml指定参数contextId的值
  5. 设置servletContext到context中
  6. 获取web.xml中配置的contextConfigLocation的值(spring的根配置文件),如果不空就设置到context中
  7. 配置context的env,暂时忽略
  8. 加载web.xml中配置的自定义上下文类(需要实现ConfigurableApplicationContext接口)暂时忽略
  9. 构建spring环境至完成
  10. 完成之后,设置context到ServletContext中,表示Spring环境已经创建完成
  11. 类加载器相关的操作 暂时忽略
  12. 监听器初始化操作执行完成,启动应用

源码如下:

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    //判断spring容器是否已经存在,如果存在则抛出异常,不能存在两个spring容器
    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 {
         //创建WebApplicationContext对象,该对象的默认实现放在spring-web jar包下的ContextLoader.properties中
        //默认为org.springframework.web.context.support.XmlWebApplicationContext
        if (this.context == null) {
           //该方法创建出来的都是ConfiguableWebApplicationContext对象
           //web启动,会创建该对象
            this.context = createWebApplicationContext(servletContext);
        }
       
        if (this.context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
            if (!cwac.isActive()) {
                // The context has not yet been refreshed -> provide services such as
                // setting the parent context, setting the application context id, etc
                 //设置父类,可以不用考虑,需在在web.xml中指定才会设置
                if (cwac.getParent() == null) {
                    // The context instance was injected without an explicit parent ->
                    // determine parent for root web application context, if any.
                    ApplicationContext parent = loadParentContext(servletContext);
                    cwac.setParent(parent);
                }
                //重点操作,配置并更新WebApplicationContext
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }

//将webApplicationContext存到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;
    }
}

欢迎扫描下方二维码,关注公众号,我们可以进行技术交流,共同成长

《Spring系列一》-Spring与tomcat的不解之缘_第1张图片
qrcode_for_gh_5580beb3cba1_430.jpg

你可能感兴趣的:(《Spring系列一》-Spring与tomcat的不解之缘)