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

引言

tomcat启动后,到底对spring和spring mvc做了什么?

介绍

本文主要介绍:spring 整合了 spring mvc后,当tomcat启动时,spring 以及 spring mvc的相互以及关系启动顺序。

典型的父子关系

爸爸对儿子说:

1,"儿子,爸爸要离开这个家,等你长大了,就来某某地方找我"

2,"儿子,你有什么要求,尽管给老爸提,老爸能满足的都满足"

感动的背后 还是充满了内疚啊

启动顺序

  • tomcat启动。
  • spring的监听器监听ServletContext。
  • 初始化ServletContext时,马上初始化spring(父),并将spring存入ServletContext中(和儿子的约定的地方)。
  • 第一次请求DispatcherServlet时,初始化 spring mvc(子),并且会第一时间去ServletContext中找spring(父)。
  • spirng mvc 需要什么资源(dao,service等) spring 只要有的都会给他,反过却不是,spring 并不会到spring mvc中去要资源(controller等)。

总结

好好感受一下筷子兄弟《父亲》。

正题

在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中取出这些参数,并进行响应的操作。

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

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;
    }
}

作者:逍遥无极
链接:https://www.jianshu.com/p/3be45ce2256a

 

史上最强Tomcat8性能优化

阿里巴巴为什么能抗住90秒100亿?--服务端高并发分布式架构演进之路

B2B电商平台--ChinaPay银联电子支付功能

学会Zookeeper分布式锁,让面试官对你刮目相看

SpringCloud电商秒杀微服务-Redisson分布式锁方案

查看更多好文,进入公众号--撩我--往期精彩

一只 有深度 有灵魂 的公众号0.0

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值