1. 前言

因为SpringBoot 内置了Tomcat容器,所以可以把 web 程序打成 jar 包,直接启动,而且在开发中也不需要像传统Spring项目那样,需要把项目添加到Tomcat中进行启动。不得不说,Springboot确实在方方面面提高了开发效率。

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_spring boot

2. 从 Main 方法说起

SpringBoot 项目有一个启动类,启动类里有一个 main 方法用来启动整个应用:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_tomcat_02


run()方法调用链:

  1. SpringApplication.run(DemoApplication.class, args)
  2. run(new Class<?>[] { primarySource }, args)
  3. new SpringApplication(primarySources).run(args)

最后跟踪到:SpringApplication.run()方法:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_tomcat_03


各步骤说明如下:

  1. 设置系统属性『java.awt.headless』,为true则启用headless模式支持;
  2. 通过 SpringFactoriesLoader 检索 META-INF/spring.factories ,找到声明的所有SpringApplicationRunListener的实现类并将其实例化,之后逐个调用其started()方法,广播SpringBoot要开始执行了;
  3. 发布应用开始启动事件;
  4. 初始化参数
  5. 创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
  6. 打印banner;
  7. 创建应用上下文;
  8. 通过SpringFactoriesLoader检索META-INF/spring.factories,获取并实例化异常分析器;
  9. 为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,这里就包括通过@EnableAutoConfiguration导入的各种自动配置类;
  10. 刷新上下文;
  11. 再一次刷新上下文,其实是空方法,可能是为了后续扩展;
  12. 发布应用已经启动的事件;
  13. 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法(可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展);
  14. 应用已经启动完成的监听事件。

简单的总结下步骤为:1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出 banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 再刷新上下文 > 9. 发布应用已经启动事件 > 10. 发布应用启动完成事件。

其实上面这段代码,如果只分析 tomcat ,只需要关注上下文是如何创建的和上下文是如何刷新的,分别对应的方法就是 createApplicationContext() 和 refreshContext(context),接下来看看这两个方法。

createApplicationContext()方法:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_Server_04


这里就是根据 webApplicationType 来判断创建哪种类型的 Servlet,代码中分别对应着 Web 类型(SERVLET),响应式 Web 类型(REACTIVE),非 Web 类型(default),我们建立的是 Web 类型,所以肯定实例化 DEFAULT_SERVLET_WEB_CONTEXT_CLASS 指定的类,也就是 AnnotationConfigServletWebServerApplicationContext 类,下面用图来说明下这个类的继承关系:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_初始化_05


通过类继承关系可知,这个类继承的是 ServletWebServerApplicationContext,而ServletWebServerApplicationContext最后继承了AbstractApplicationContext。refreshContext(context)方法:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_初始化_06


spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_tomcat_07


然后调用AbstractApplicationContext的refresh()方法:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_Web_08


其中onRefresh()方法利用模板方法模式,是一个空方法,留给子类实现:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_Web_09


看ServletWebServerApplicationContext中的onRefresh()方法:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_tomcat_10


顾名思义,createWebServer()就是创建web服务的:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_tomcat_11


从代码中可知,webServer是从ServletWebServerFactory工厂中获取的,ServletWebServerFactory是一个接口,实现类有:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_spring boot_12


所以也可以手动指定内嵌容器为其它容器。

3. 走进 Tomcat

直接进入TomcatServletWebServerFactory的getWebServer()方法:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_spring boot_13


该方法主要做了两件事情:

  • 把 Connnctor(我们称之为连接器)对象添加到 Tomcat 中;
  • configureEngine

跟踪configureEngine()方法:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_初始化_14


这个 Engine 其实就是容器Container的子类。继续跟踪源码,找到Container接口,看其实现:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_tomcat_15


其中有4个子接口,分别是Engine,Host,Context,Wrapper。看源码注释可知Engine 是最高级别的容器,其子容器是 Host,Host 的子容器是 Context,Wrapper 是 Context 的子容器,所以这 4 个容器的关系就是父子关系,也就是 Engine>Host>Context>Wrapper。

再看Tomcat源码:

//部分源码,其余部分省略。
public class Tomcat {
	 //设置连接器
     public void setConnector(Connector connector) {
        Service service = getService();
        boolean found = false;
        for (Connector serviceConnector : service.findConnectors()) {
            if (connector == serviceConnector) {
                found = true;
            }
        }
        if (!found) {
            service.addConnector(connector);
        }
    }
    //获取service
    public Service getService() {
        return getServer().findServices()[0];
    }
    //设置Host容器
    public void setHost(Host host) {
        Engine engine = getEngine();
        boolean found = false;
        for (Container engineHost : engine.findChildren()) {
            if (engineHost == host) {
                found = true;
            }
        }
        if (!found) {
            engine.addChild(host);
        }
    }
    //获取Engine容器
    public Engine getEngine() {
        Service service = getServer().findServices()[0];
        if (service.getContainer() != null) {
            return service.getContainer();
        }
        Engine engine = new StandardEngine();
        engine.setName( "Tomcat" );
        engine.setDefaultHost(hostname);
        engine.setRealm(createDefaultRealm());
        service.setContainer(engine);
        return engine;
    }
    //获取server
    public Server getServer() {

        if (server != null) {
            return server;
        }

        System.setProperty("catalina.useNaming", "false");

        server = new StandardServer();

        initBaseDir();

        // Set configuration source
        ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));

        server.setPort( -1 );

        Service service = new StandardService();
        service.setName("Tomcat");
        server.addService(service);
        return server;
    }

    //添加Context容器
    public Context addContext(Host host, String contextPath, String contextName,
            String dir) {
        silence(host, contextName);
        Context ctx = createContext(host, contextPath);
        ctx.setName(contextName);
        ctx.setPath(contextPath);
        ctx.setDocBase(dir);
        ctx.addLifecycleListener(new FixContextListener());

        if (host == null) {
            getHost().addChild(ctx);
        } else {
            host.addChild(ctx);
        }

    //添加Wrapper容器
    public static Wrapper addServlet(Context ctx,
                                      String servletName,
                                      Servlet servlet) {
        // will do class for name and set init params
        Wrapper sw = new ExistingStandardWrapper(servlet);
        sw.setName(servletName);
        ctx.addChild(sw);

        return sw;
    }
}

从 Tomcat 的 getServer() 可以知道,Tomcat 的最顶层是 Server,Server 就是 Tomcat 的实例,一个 Tomcat 一个 Server;通过 getEngine() 了解到 Server 下面是 Service,而且是多个,一个 Service 代表部署的一个应用,而且还可以知道,Engine 容器,一个 service 只有一个。

根据父子关系,看 setHost()源码可以知道,host 容器有多个;同理,从 addContext()源码下知道,Context 也是多个;addServlet()表明 Wrapper 容器也是多个,而且这段代码也暗示了,其实 Wrapper 和 Servlet 是一层意思。另外根据 setConnector 源码可以知道,连接器(Connector)是设置在 service 下的,而且是可以设置多个连接器(Connector)。

根据上面分析可知:Tomcat 主要包含了 2 个核心组件,连接器(Connector)和容器(Container),用图表示如下:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_初始化_16


一个 Tomcat 是一个 Server,一个 Server 下有多个 service,也就是我们部署的多个应用,一个应用下有多个连接器(Connector)和一个容器(Container),容器下有多个子容器,关系用图表示如下:

spring boot嵌入jetty服务器配置 springboot如何内嵌tomcat_spring boot_17


Engine 下有多个 Host 子容器,Host 下有多个 Context 子容器,Context 下有多个 Wrapper 子容器。

4. 总结

SpringBoot 的启动是通过 new SpringApplication()实例来启动的,启动过程主要做如下几件事情:> 1. 配置属性 > 2. 获取监听器,发布应用开始启动事件 > 3. 初始化输入参数 > 4. 配置环境,输出 banner > 5. 创建上下文 > 6. 预处理上下文 > 7. 刷新上下文 > 8. 再刷新上下文 > 9. 发布应用已经启动事件 > 10. 发布应用启动完成事件。

而启动 Tomcat 就是在第 7 步“刷新上下文”中;Tomcat 的启动主要是初始化 2 个核心组件,连接器(Connector)和容器(Container),一个 Tomcat 实例就是一个 Server,一个 Server 包含多个 Service,也就是多个应用程序,每个 Service 包含多个连接器(Connetor)和一个容器(Container),而容器下又有多个子容器,按照父子关系分别为:Engine,Host,Context,Wrapper,其中除了 Engine 外,其余的容器都是可以有多个。