Spring Boot 源码分析 - 内嵌Tomcat容器的实现

参考 知识星球芋道源码 星球的源码解析,一个活跃度非常高的 Java 技术社群,感兴趣的小伙伴可以加入 芋道源码 星球,一起学习😄

该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读

Spring Boot 版本:2.2.x

最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 - 文章导读》 系列文章

如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~

该系列其他文章请查看:《精尽 Spring Boot 源码分析 - 文章导读》

概述

我们知道 Spring Boot 能够创建独立的 Spring 应用,内部嵌入 Tomcat 容器(Jetty、Undertow),让我们的 jar 无需放入 Servlet 容器就能直接运行。那么对于 Spring Boot 内部嵌入 Tomcat 容器的实现你是否深入的学习过?或许你可以通过这篇文章了解到相关内容。

在上一篇 《SpringApplication 启动类的启动过程》 文章分析了 SpringApplication#run(String... args) 启动 Spring 应用的主要流程,不过你是不是没有看到和 Tomcat 相关的初始化工作呢?

别急,在刷新 Spring 应用上下文的过程中会调用 onRefresh() 方法,在 Spring Boot 的 ServletWebServerApplicationContext 中重写了该方法,此时会创建一个 Servlet 容器(默认为 Tomcat),并添加 IoC 容器中的 Servlet、Filter 和 EventListener 至 Servlet 上下文。

例如 Spring MVC 中的核心组件 DispatcherServlet 对象会添加至 Servlet 上下文,不熟悉 Spring MVC 的小伙伴可查看我前面的 《精尽Spring MVC源码分析 - 一个请求的旅行过程》 这篇文章。同时,在 《精尽Spring MVC源码分析 - 寻找遗失的 web.xml》 这篇文章中有提到过 Spring Boot 是如何加载 Servlet 的,感兴趣的可以先去看一看,本文会做更加详细的分析。

接下来,我们一起来看看 Spring Boot 内嵌 Tomcat 的实现。

文章的篇幅有点长,处理过程有点绕,每个小节我都是按照优先顺序来展述的,同时,主要的流程也标注了序号,请耐心查看📝

如何使用

在我们的 Spring Boot 项目中通常会引入 spring-boot-starter-web 这个依赖,该模块提供全栈的 WEB 开发特性,包括 Spring MVC 依赖和 Tomcat 容器,这样我们就可以打成 jar 包直接启动我们的应用,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

如果不想使用内嵌的 Tomcat,我们可以这样做:

<packaging>war</packaging>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
    <scope>provided</scope>
</dependency>

然后启动类这样写:

// 方式三
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    // 可不写
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Application.class);
    }
}

这样你打成 war 包就可以放入外部的 Servlet 容器中运行了,具体实现查看下一篇文章,本文分析的主要是 Spring Boot 内嵌 Tomcat 的实现。

回顾

在上一篇 《SpringApplication 启动类的启动过程》 文章分析 SpringApplication#run(String... args) 启动 Spring 应用的过程中讲到,在创建好 Spring 应用上下文后,会调用其 AbstractApplication#refresh() 方法刷新上下文,该方法涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 - Spring 应用上下文 ApplicationContext》

/**
 * 刷新上下文,在哪会被调用?
 * 在 **Spring MVC** 中,{@link org.springframework.web.context.ContextLoader#initWebApplicationContext} 方法初始化上下文时,会调用该方法
 */
@Override
public void refresh() throws BeansException, IllegalStateException {
    // <1> 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
    synchronized (this.startupShutdownMonitor) {

        // <2> 刷新上下文环境的准备工作,记录下容器的启动时间、标记'已启动'状态、对上下文环境属性进行校验
        prepareRefresh();

        // <3> 创建并初始化一个 BeanFactory 对象 `beanFactory`,会加载出对应的 BeanDefinition 元信息们
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // <4> 为 `beanFactory` 进行一些准备工作,例如添加几个 BeanPostProcessor,手动注册几个特殊的 Bean
        prepareBeanFactory(beanFactory);

        try {
            // <5> 对 `beanFactory` 在进行一些后期的加工,交由子类进行扩展
            postProcessBeanFactory(beanFactory);

            // <6> 执行 BeanFactoryPostProcessor 处理器,包含 BeanDefinitionRegistryPostProcessor 处理器
            invokeBeanFactoryPostProcessors(beanFactory);

            // <7> 对 BeanPostProcessor 处理器进行初始化,并添加至 BeanFactory 中
            registerBeanPostProcessors(beanFactory);

            // <8> 设置上下文的 MessageSource 对象
            initMessageSource();

            // <9> 设置上下文的 ApplicationEventMulticaster 对象,上下文事件广播器
            initApplicationEventMulticaster();

            // <10> 刷新上下文时再进行一些初始化工作,交由子类进行扩展
            onRefresh();

            // <11> 将所有 ApplicationListener 监听器添加至 `applicationEventMulticaster` 事件广播器,如果已有事件则进行广播
            registerListeners();

            // <12> 设置 ConversionService 类型转换器,**初始化**所有还未初始化的 Bean(不是抽象、单例模式、不是懒加载方式)
            finishBeanFactoryInitialization(beanFactory);

            // <13> 刷新上下文的最后一步工作,会发布 ContextRefreshedEvent 上下文完成刷新事件
            finishRefresh();
        }
        // <14> 如果上面过程出现 BeansException 异常
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }
            // <14.1> “销毁” 已注册的单例 Bean
            destroyBeans();

            // <14.2> 设置上下文的 `active` 状态为 `false`
            cancelRefresh(ex);

            // <14.3> 抛出异常
            throw ex;
        }
        // <15> `finally` 代码块
        finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            // 清除相关缓存,例如通过反射机制缓存的 Method 和 Field 对象,缓存的注解元数据,缓存的泛型类型对象,缓存的类加载器
            resetCommonCaches();
        }
    }
}

在该方法的第 10 步可以看到会调用 onRefresh() 方法再进行一些初始化工作,这个方法交由子类进行扩展,那么在 Spring Boot 中的 ServletWebServerApplicationContext 重写了该方法,会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境。

13 步会调用 onRefresh() 方法,ServletWebServerApplicationContext 重写了该方法,启动 WebServer,对 Servlet 进行加载并初始化

类图

由于整个 ApplicationContext 体系比较庞大,下面列出了部分类

DispatcherServlet 自动配置类

在开始之前,我们先来看看 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration 这个自动配置类,部分如下:

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) // 最高优先级的自动配置
@Configuration(proxyBeanMethods = false) // 作为一个配置类,不进行 CGLIB 提升
@ConditionalOnWebApplication(type = Type.SERVLET) // Servlet 应用的类型才注入当前 Bean
@ConditionalOnClass(DispatcherServlet.class) // 存在 DispatcherServlet 这个类才注入当前 Bean
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class) // 在 ServletWebServerFactoryAutoConfiguration 后面进行自动配置
public class DispatcherServletAutoConfiguration {
    
	public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";

	public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";

	// 作为一个配置类,不进行 CGLIB 提升
	@Configuration(proxyBeanMethods = false)
	// 满足条件则注入当前 DispatcherServlet(需要 Spring 上下文中不存在)
	@Conditional(DefaultDispatcherServletCondition.class)
	// 存在 ServletRegistration 这个类才注入当前 Bean
	@ConditionalOnClass(ServletRegistration.class)
	// 注入两个配置对象
	@EnableConfigurationProperties({ HttpProperties.class, WebMvcProperties.class })
	protected static class DispatcherServletConfiguration {

		// 定义一个 DispatcherServlet 的 Bean
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServlet dispatcherServlet(HttpProperties httpProperties, WebMvcProperties webMvcProperties) {
			DispatcherServlet dispatcherServlet = new DispatcherServlet();
			dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
			dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
			dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
			dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
			dispatcherServlet.setEnableLoggingRequestDetails(httpProperties.isLogRequestDetails());
			return dispatcherServlet;
		}

		@Bean
		@ConditionalOnBean(MultipartResolver.class)
		@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
		public MultipartResolver multipartResolver(MultipartResolver resolver) {
			// Detect if the user has created a MultipartResolver but named it incorrectly
			return resolver;
		}
	}

	// 作为一个配置类,不进行 CGLIB 提升
	@Configuration(proxyBeanMethods = false)
	// 满足条件则注入当前 DispatcherServletRegistrationBean
	@Conditional(DispatcherServletRegistrationCondition.class)
	// 存在 ServletRegistration 这个类才注入当前 Bean
	@ConditionalOnClass(ServletRegistration.class)
	// 注入一个配置对象
	@EnableConfigurationProperties(WebMvcProperties.class)
	// 先注入上面的 DispatcherServletConfiguration 对象
	@Import(DispatcherServletConfiguration.class)
	protected static class DispatcherServletRegistrationConfiguration {

		// 为 DispatcherServlet 定义一个 RegistrationBean 对象,目的是往 ServletContext 上下文中添加 DispatcherServlet
		@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
		// 需要存在名称为 `dispatcherServlet` 类型为 DispatcherServlet 的 Bean
		@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
		public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
				WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
			DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
					webMvcProperties.getServlet().getPath());
			registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
			registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
			// 如果有 MultipartConfigElement 配置则进行设置
			multipartConfig.ifAvailable(registration::setMultipartConfig);
			return registration;
		}
	}
}

这个 DispatcherServletAutoConfiguration 自动配置类,会在你引入 spring-boot-starter-web 模块后生效,因为该模块引入了 spring mvctomcat 相关依赖,关于 Spring Boot 的自动配置功能在后续文章进行分析。

在这里会注入 DispatcherServletRegistrationBean(继承 RegistrationBean )对象,它关联着一个 DispatcherServlet 对象。在后面会讲到 Spring Boot 会找到所有 RegistrationBean对象,然后往 Servlet 上下文中添加 Servlet 或者 Filter。

ServletWebServerApplicationContext

org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext,Spring Boot 应用 SERVLET 类型(默认)对应的 Spring 上下文对象

接下来,我们一起来看看它重写的 onRefresh()finishRefresh() 方法

1. onRefresh 方法

// ServletWebServerApplicationContext.java
@Override
protected void onRefresh() {
    // 调用父类方法,初始化 ThemeSource 对象
    super.onRefresh();
    try {
        /**
         * 创建一个 WebServer 服务(默认 Tomcat),并初始化 ServletContext 上下文
         * 会先创建一个 {@link Tomcat} 容器并启动,同时会注册各种 Servlet
         * 例如 借助 {@link org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration}
         * 注册 {@link DispatcherServlet} 对象到 ServletContext 上下文,这样就可以通过 Spring MVC 的核心组件来实现一个 Web 应用
         */
        createWebServer();
    }
    catch (Throwable ex) {
        throw new ApplicationContextException("Unable to start web server", ex);
    }
}

首先会调用父类方法,初始化 ThemeSource 对象,然后调用自己的 createWebServer() 方法,创建一个 WebServer 服务(默认 Tomcat),并初始化 ServletContext 上下文,如下:

// ServletWebServerApplicationContext.java
private void createWebServer() {
    // <1> 获取当前 `WebServer` 容器对象,首次进来为空
    WebServer webServer = this.webServer;
    // <2> 获取 `ServletContext` 上下文对象
    ServletContext servletContext = getServletContext();
    // <3> 如果 WebServer 和 ServletContext 都为空,则需要创建一个
    // 使用 Spring Boot 内嵌 Tomcat 容器则会进入该分支
    if (webServer == null && servletContext == null) {
        // <3.1> 获取 Servlet 容器工厂对象(默认为 Tomcat)`factory`
        ServletWebServerFactory factory = getWebServerFactory();
        /**
         * <3.2> 先创建一个 {@link ServletContextInitializer} Servlet 上下文初始器,实现也就是当前类的 {@link this#selfInitialize(ServletContext)} 方法
         * 至于为什么不用 Servlet 3.0 新增的 {@link javax.servlet.ServletContainerInitializer} 这个类,我在
         * [精尽Spring MVC源码分析 - 寻找遗失的 web.xml](https://www.cnblogs.com/lifullmoon/p/14122704.html)有提到过
         *
         * <3.3> 从 `factory` 工厂中创建一个 WebServer 容器对象
         * 例如创建一个 {@link TomcatWebServer} 容器对象,并初始化 `ServletContext` 上下文,创建 {@link Tomcat} 容器并启动
         * 启动过程异步触发了 {@link org.springframework.boot.web.embedded.tomcat.TomcatStarter#onStartup} 方法
         * 也就会调用这个传入的 {@link ServletContextInitializer} 的 {@link #selfInitialize(ServletContext)} 方法
         */
        this.webServer = factory.getWebServer(getSelfInitializer());
    }
    // <4> 否则,如果 ServletContext 不为空,说明使用了外部的 Servlet 容器(例如 Tomcat)
    else if (servletContext != null) {
        try {
            /** 那么这里主动调用 {@link this#selfInitialize(ServletContext)} 方法来注册各种 Servlet、Filter */
            getSelfInitializer().onStartup(servletContext);
        }
        catch (ServletException ex) {
            throw new ApplicationContextException("Cannot initialize servlet context", ex);
        }
    }
    // <5> 将 ServletContext 的一些初始化参数关联到当前 Spring 应用的 Environment 环境中
    initPropertySources();
}

过程如下:

  1. 获取当前 WebServer 容器对象,首次进来为

  2. 获取 ServletContext 上下文对象

    @Override
    @Nullable
    public ServletContext getServletContext() {
        return this.servletContext;
    }
    
  3. 如果 WebServerServletContext 都为空,则需要创建一个,此时使用 Spring Boot 内嵌 Tomcat 容器则会进入该分支

    1. 获取 Servlet 容器工厂对象(默认为 Tomcat)factory,如下:

      protected ServletWebServerFactory getWebServerFactory() {
          // Use bean names so that we don't consider the hierarchy
          // 获取当前 BeanFactory 中类型为 ServletWebServerFactory 的 Bean 的名称,不考虑层次性
          // 必须存在一个,否则抛出异常
          // 所以想要切换 Servlet 容器得引入对应的 Starter 模块并排除 `spring-boot-starter-web` 中默认的 `tomcat` Starter 模块
          String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
          if (beanNames.length == 0) {
              throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to missing "
                      + "ServletWebServerFactory bean.");
          }
          if (beanNames.length > 1) {
              throw new ApplicationContextException("Unable to start ServletWebServerApplicationContext due to multiple "
                      + "ServletWebServerFactory beans : " + StringUtils.arrayToCommaDelimitedString(beanNames));
          }
          // 获取这个 ServletWebServerFactory 对象
          return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
      }
      

      spring-boot-autoconfigure 中有一个 ServletWebServerFactoryConfiguration 配置类会注册一个 TomcatServletWebServerFactory 对象

      加上 TomcatServletWebServerFactoryCustomizer 自动配置类,可以将 server.* 相关的配置设置到该对象中,这一步不深入分析,感兴趣可以去看一看

    2. 先创建一个 ServletContextInitializer Servlet 上下文初始器,实现也就是当前类的 this#selfInitialize(ServletContext) 方法,如下:

      private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
          return this::selfInitialize;
      }
      

      这个 ServletContextInitializer 在后面会被调用,请记住这个方法

    3. factory 工厂中创建一个 WebServer 容器对象,例如创建一个 TomcatWebServer 容器对象,并初始化 ServletContext 上下文,该过程会创建一个 Tomcat 容器并启动,启动过程异步触发了 TomcatStarter#onStartup 方法,也就会调用第 2 步的 ServletContextInitializer#selfInitialize(ServletContext) 方法

  4. 否则,如果 ServletContext 不为空,说明使用了外部的 Servlet 容器(例如 Tomcat)

    1. 那么这里主动调用 this#selfInitialize(ServletContext) 方法来注册各种 Servlet、Filter
  5. 将 ServletContext 的一些初始化参数关联到当前 Spring 应用的 Environment 环境中

整个过程有点绕,如果获取到的 WebServerServletContext 都为空,说明需要使用内嵌的 Tomcat 容器,那么第 3 步就开始进行 Tomcat 的初始化工作;

这里第 4 步的分支也很关键,如果 ServletContext 不为空,说明使用了外部的 Servlet 容器(例如 Tomcat),关于 Spring Boot 应用打成 war 包支持放入外部的 Servlet 容器运行的原理在下一篇文章进行分析。

TomcatServletWebServerFactory

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory,Tomcat 容器工厂,用于创建 TomcatWebServer 对象

1.1 getWebServer 方法

getWebServer(ServletContextInitializer... initializers) 方法,创建一个 TomcatWebServer 容器对象,并初始化 ServletContext 上下文,创建 Tomcat 容器并启动

// TomcatServletWebServerFactory.java
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
    if (this.disableMBeanRegistry) {
        // <1> 禁用 MBean 注册中心
        Registry.disableRegistry();
    }
    // <2> 创建一个 Tomcat 对象 `tomcat`
    Tomcat tomcat = new Tomcat();
    // <3> 创建一个临时目录(退出时删除)
    File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
    // <4> 将这个目录作为 Tomcat 的目录
    tomcat.setBaseDir(baseDir.getAbsolutePath());

    // <5> 创建一个 NIO 协议的 Connector 连接器对象,并添加到第 `2` 步创建的 `tomcat` 中
    Connector connector = new Connector(this.protocol);
    connector.setThrowOnFailure(true);
    tomcat.getService().addConnector(connector);
    // <6> 对 Connector 进行配置,设置 `server.port` 端口、编码
    // `server.tomcat.min-spare-threads` 最小空闲线程和 `server.tomcat.accept-count` 最大线程数
    customizeConnector(connector);
    tomcat.setConnector(connector);
    // <7> 禁止自动部署
    tomcat.getHost().setAutoDeploy(false);
    configureEngine(tomcat.getEngine());
    // <8> 同时支持多个 Connector 连接器(默认没有)
    for (Connector additionalConnector : this.additionalTomcatConnectors) {
        tomcat.getService().addConnector(additionalConnector);
    }
    // <9> 创建一个 TomcatEmbeddedContext 上下文对象,并进行初始化工作,配置 TomcatStarter 作为启动器
    // 会将这个上下文对象设置到当前 `tomcat` 中去
    prepareContext(tomcat.getHost(), initializers);
    /**
     * <10> 创建一个 TomcatWebServer 容器对象,是对 `tomcat` 的封装,用于控制 Tomcat 服务器
     * 同时初始化 Tomcat 容器并启动,这里会异步触发了 {@link TomcatStarter#onStartup} 方法
     * 也就会调用入参中几个 {@link ServletContextInitializer#onStartup} 方法
     * 例如 {@link org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#selfInitialize}
     */
    return getTomcatWebServer(tomcat);
}

过程如下:

  1. 禁用 MBean 注册中心

  2. 创建一个 Tomcat 对象 tomcat

  3. 创建一个临时目录(退出时删除)

    protected final File createTempDir(String prefix) {
        try {
            // 创建一个临时目录,临时目录下的 `tomcat.端口号` 目录
            File tempDir = Files.createTempDirectory(prefix + "." + getPort() + ".").toFile();
            // 应用退出时会删除
            tempDir.deleteOnExit();
            return tempDir;
        } catch (IOException ex) {
            throw new WebServerException(
                    "Unable to create tempDir. java.io.tmpdir is set to " + System.getProperty("java.io.tmpdir"), ex);
        }
    }
    
  4. 将这个临时目录作为 Tomcat 的目录

  5. 创建一个 NIO 协议的 Connector 连接器对象,并添加到第 2 步创建的 tomcat

  6. 对 Connector 进行配置,设置 server.port 端口、编码、server.tomcat.min-spare-threads 最小空闲线程和 server.tomcat.accept-count 最大线程数。这些配置就是我们自己配置的,在前面 1. onRefresh 方法 的第 3 步有提到

    protected void customizeConnector(Connector connector) {
        // 获取端口(也就是 `server.port`),并设置
        int port = Math.max(getPort(), 0);
        connector.setPort(port);
        if (StringUtils.hasText(this.getServerHeader())) {
            connector.setAttribute("server", this.getServerHeader());
        }
        if (connector.getProtocolHandler() instanceof AbstractProtocol) {
            customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
        }
        invokeProtocolHandlerCustomizers(connector.getProtocolHandler());
        // 设置编码
        if (getUriEncoding() != null) {
            connector.setURIEncoding(getUriEncoding().name());
        }
        // Don't bind to the socket prematurely if ApplicationContext is slow to start
        connector.setProperty("bindOnInit", "false");
        if (getSsl() != null && getSsl().isEnabled()) {
            customizeSsl(connector);
        }
        TomcatConnectorCustomizer compression = new CompressionConnectorCustomizer(getCompression());
        compression.customize(connector);
        for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
            // 借助 TomcatWebServerFactoryCustomizer 对 Connector 进行配置
            // 例如设置 `server.tomcat.min-spare-threads` 最小空闲线程和 `server.tomcat.accept-count` 最大线程数
            customizer.customize(connector);
        }
    }
    
  7. 禁止自动部署

  8. 同时支持多个 Connector 连接器(默认没有)

  9. 调用 prepareContext(..) 方法,创建一个 TomcatEmbeddedContext 上下文对象,并进行初始化工作,配置 TomcatStarter 作为启动器,会将这个上下文对象设置到当前 tomcat 中去

  10. 调用 getTomcatWebServer(Tomcat) 方法,创建一个 TomcatWebServer 容器对象,是对 tomcat 的封装,用于控制 Tomcat 服务器

整个 Tomcat 的初始化过程没有特别的复杂,主要是因为这里没有深入分析,我们知道大致的流程即可,这里我们重点关注第 910 步,接下来依次分析

1.1.1 prepareContext 方法

prepareContext(Host, ServletContextInitializer[]) 方法,创建一个 TomcatEmbeddedContext 上下文对象,并进行初始化工作,配置 TomcatStarter 作为启动器,会将这个上下文对象设置到 Tomcat 的 Host 中去,如下:

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
    File documentRoot = getValidDocumentRoot();
    // <1> 创建一个 TomcatEmbeddedContext 上下文对象 `context`
    TomcatEmbeddedContext context = new TomcatEmbeddedContext();
    if (documentRoot != null) {
        context.setResources(new LoaderHidingResourceRoot(context));
    }
    context.setName(getContextPath());
    context.setDisplayName(getDisplayName());
    // <2> 设置 `context-path`
    context.setPath(getContextPath());
    File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
    // <3> 设置 Tomcat 根目录
    context.setDocBase(docBase.getAbsolutePath());
    context.addLifecycleListener(new FixContextListener());
    context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
            : ClassUtils.getDefaultClassLoader());
    resetDefaultLocaleMapping(context);
    addLocaleMappings(context);
    try {
        context.setCreateUploadTargets(true);
    } catch (NoSuchMethodError ex) {
        // Tomcat is < 8.5.39. Continue.
    }
    configureTldPatterns(context);
    WebappLoader loader = new WebappLoader();
    loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
    loader.setDelegate(true);
    context.setLoader(loader);
    if (isRegisterDefaultServlet()) {
        // <4> 注册默认的 Servlet 为 `org.apache.catalina.servlets.DefaultServlet`
        addDefaultServlet(context);
    }
    if (shouldRegisterJspServlet()) {
        addJspServlet(context);
        addJasperInitializer(context);
    }
    context.addLifecycleListener(new StaticResourceConfigurer(context));
    ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
    // <5> 将这个 `context` 上下文对象添加到 `tomcat` 中去
    host.addChild(context);
    // <6> 对 TomcatEmbeddedContext 进行配置,例如配置 TomcatStarter 启动器,它是对 ServletContext 上下文对象的初始器 `initializersToUse` 的封装
    configureContext(context, initializersToUse);
    postProcessContext(context);
}

整个过程我们挑主要的流程来看:

  1. 创建一个 TomcatEmbeddedContext 上下文对象 context,接下来进行一系列的配置

  2. 设置 context-path

  3. 设置 Tomcat 根目录

  4. 注册默认的 Servlet 为 org.apache.catalina.servlets.DefaultServlet

    private void addDefaultServlet(Context context) {
        Wrapper defaultServlet = context.createWrapper();
        defaultServlet.setName("default");
        defaultServlet.setServletClass("org.apache.catalina.servlets.DefaultServlet");
        defaultServlet.addInitParameter("debug", "0");
        defaultServlet.addInitParameter("listings", "false");
        defaultServlet.setLoadOnStartup(1);
        // Otherwise the default location of a Spring DispatcherServlet cannot be set
        defaultServlet.setOverridable(true);
        context.addChild(defaultServlet);
        context.addServletMappingDecoded("/", "default");
    }
    
  5. 将这个 context 上下文对象添加到 tomcat 中去

  6. 调用 configureContext(..) 方法,对 context 进行配置,例如配置 TomcatStarter 启动器,它是对 ServletContext 上下文对象的初始器 initializersToUse 的封装

可以看到 Tomcat 上下文对象设置了 context-path,也就是我们的配置的 server.servlet.context-path 属性值。

同时,在第 6 步会调用方法对 Tomcat 上下文对象进一步配置

1.1.2 configureContext 方法

configureContext(Context, ServletContextInitializer[]) 方法,对 Tomcat 上下文对象,主要配置 TomcatStarter 启动器,如下:

protected void configureContext(Context context, ServletContextInitializer[] initializers) {
    // <1> 创建一个 TomcatStarter 启动器,此时把 ServletContextInitializer 数组传入进去了
    // 并设置到 TomcatEmbeddedContext 上下文中
    TomcatStarter starter = new TomcatStarter(initializers);
    if (context instanceof TomcatEmbeddedContext) {
        TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
        embeddedContext.setStarter(starter);
        embeddedContext.setFailCtxIfServletStartFails(true);
    }
    context.addServletContainerInitializer(starter, NO_CLASSES);
    for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) {
        context.addLifecycleListener(lifecycleListener);
    }
    for (Valve valve : this.contextValves) {
        context.getPipeline().addValve(valve);
    }
    // <2> 设置错误页面
    for (ErrorPage errorPage : getErrorPages()) {
        org.apache.tomcat.util.descriptor.web.ErrorPage tomcatErrorPage = new org.apache.tomcat.util.descriptor.web.ErrorPage();
        tomcatErrorPage.setLocation(errorPage.getPath());
        tomcatErrorPage.setErrorCode(errorPage.getStatusCode());
        tomcatErrorPage.setExceptionType(errorPage.getExceptionName());
        context.addErrorPage(tomcatErrorPage);
    }
    for (MimeMappings.Mapping mapping : getMimeMappings()) {
        context.addMimeMapping(mapping.getExtension(), mapping.getMimeType());
    }
    // <3> 配置 TomcatEmbeddedContext 上下文的 Session 会话,例如超时会话时间
    configureSession(context);
    new DisableReferenceClearingContextCustomizer().customize(context);
    // <4> 对 TomcatEmbeddedContext 上下文进行自定义处理,例如添加 WsContextListener 监听器
    for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) {
        customizer.customize(context);
    }
}

配置过程如下:

  1. 创建一个 TomcatStarter 启动器,此时把 ServletContextInitializer 数组传入进去了,并设置到 context 上下文中

  2. 设置错误页面

  3. 配置 context 上下文的 Session 会话,例如超时会话时间

  4. context 上下文进行自定义处理,例如添加 WsContextListener 监听器

重点来了,这里设置了一个 TomcatStarter 对象,它实现了 javax.servlet.ServletContainerInitializer 接口,目的就是触发 Spring Boot 自己的 ServletContextInitializer 这个对象。

注意,入参中的 ServletContextInitializer 数组是什么,你可以一直往回跳,有一个对象就是 ServletWebServerApplicationContext#selfInitialize(ServletContext) 这个方法,到时候会触发它。关键!!!

javax.servlet.ServletContainerInitializer 是 Servlet 3.0 新增的一个接口,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup(..) 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup(..) 处理的类。

至于为什么这样做,可参考我的 《精尽Spring MVC源码分析 - 寻找遗失的 web.xml》 这篇文章的说明

1.1.3 getTomcatWebServer 方法

getTomcatWebServer(Tomcat) 方法,创建一个 TomcatWebServer 容器对象,是对 tomcat 的封装,用于控制 Tomcat 服务器,如下:

protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
    /**
     * 创建一个 TomcatWebServer 容器对象
     * 同时初始化 Tomcat 容器并启动,这里会异步触发了 {@link TomcatStarter#onStartup} 方法
     */
    return new TomcatWebServer(tomcat, getPort() >= 0);
}

可以看到,这里创建了一个 TomcatWebServer 对象,是对 tomcat 的封装,用于控制 Tomcat 服务器,但是,Tomcat 在哪启动的呢?

别急,在它的构造方法中还有一些初始化工作

TomcatWebServer

org.springframework.boot.web.embedded.tomcat.TomcatWebServer,对 Tomcat 的封装,用于控制 Tomcat 服务器

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
    Assert.notNull(tomcat, "Tomcat Server must not be null");
    this.tomcat = tomcat;
    this.autoStart = autoStart;
    /** 初始化 Tomcat 容器,并异步触发了 {@link TomcatStarter#onStartup} 方法 */
    initialize();
}

当你创建该对象时,会调用 initialize() 方法进行一些初始化工作

1.1.4 initialize 方法

initialize() 方法,初始化 Tomcat 容器,并异步触发了 TomcatStarter#onStartup 方法

private void initialize() throws WebServerException {
    logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
    synchronized (this.monitor) {
        try {
            addInstanceIdToEngineName();

            // 找到之前创建的 TomcatEmbeddedContext 上下文
            Context context = findContext();
            context.addLifecycleListener((event) -> {
                if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
                    // Remove service connectors so that protocol binding doesn't
                    // happen when the service is started.
                    removeServiceConnectors();
                }
            });

            // Start the server to trigger initialization listeners
            /** 启动 Tomcat 容器,这里会触发初始化监听器,例如异步触发了 {@link TomcatStarter#onStartup} 方法 */
            this.tomcat.start();

            // We can re-throw failure exception directly in the main thread
            rethrowDeferredStartupExceptions();

            try {
                ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
            } catch (NamingException ex) {
                // Naming is not enabled. Continue
            }

            // Unlike Jetty, all Tomcat threads are daemon threads. We create a
            // blocking non-daemon to stop immediate shutdown
            startDaemonAwaitThread();
        } catch (Exception ex) {
            stopSilently();
            destroySilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
    }
}

可以看到,这个方法的关键在于 this.tomcat.start() 这一步,启动 Tomcat 容器,那么会触发 javax.servlet.ServletContainerInitializeronStartup(..) 方法

在上面的 1.1.2 configureContext 方法1.1.3 getTomcatWebServer 方法 小节中也讲到过,有一个 TomcatStarter 对象,也就会触发它的 onStartup(..) 方法

那么 TomcatStarter 内部封装了一些 Spring Boot 的 ServletContextInitializer 对象,其中有一个实现类是ServletWebServerApplicationContext#selfInitialize(ServletContext) 匿名方法

TomcatStarter

org.springframework.boot.web.embedded.tomcat.TomcatStarter,实现 javax.servlet.ServletContainerInitializer 接口,用于触发 Spring Boot 的 ServletContextInitializer 对象

class TomcatStarter implements ServletContainerInitializer {
    
	private final ServletContextInitializer[] initializers;

	private volatile Exception startUpException;

	TomcatStarter(ServletContextInitializer[] initializers) {
		this.initializers = initializers;
	}

	@Override
	public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {
		try {
			/**
			 * 依次执行所有的 Servlet 上下文启动器
			 * {@link ServletWebServerApplicationContext}
			 */
			for (ServletContextInitializer initializer : this.initializers) {
				initializer.onStartup(servletContext);
			}
		} catch (Exception ex) {
			this.startUpException = ex;
		}
	}

	Exception getStartUpException() {
		return this.startUpException;
	}

}

在实现方法 onStartup(..) 中逻辑比较简单,就是调用 Spring Boot 自己的 ServletContextInitializer 实现类,例如 ServletWebServerApplicationContext#selfInitialize(ServletContext) 匿名方法

至于 TomcatStarter 为什么这做,是 Spring Boot 有意而为之,我们在使用 Spring Boot 时,开发阶段一般都是使用内嵌 Tomcat 容器,但部署时却存在两种选择:一种是打成 jar 包,使用 java -jar 的方式运行;另一种是打成 war 包,交给外置容器去运行。

前者就会导致容器搜索算法出现问题,因为这是 jar 包的运行策略,不会按照 Servlet 3.0 的策略去加载 ServletContainerInitializer

所以 Spring Boot 提供了 ServletContextInitializer 去替代。

2. selfInitialize 方法

该方法在 org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext 中,如下:

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
    return this::selfInitialize;
}

思路是不是清晰明了了,前面一直没有提到 Servlet 和 Filter 是在哪添加至 Servlet 上下文中的,答案将在这里被揭晓

private void selfInitialize(ServletContext servletContext) throws ServletException {
    // <1> 将当前 Spring 应用上下文设置到 ServletContext 上下文的属性中
    // 同时将 ServletContext 上下文设置到 Spring 应用上下文中
    prepareWebApplicationContext(servletContext);
    // <2> 向 Spring 应用上下文注册一个 ServletContextScope 对象(ServletContext 的封装)
    registerApplicationScope(servletContext);
    // <3> 向 Spring 应用上下文注册 `contextParameters` 和 `contextAttributes` 属性(会先被封装成 Map)
    WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
    /**
     * <4> 【重点】先从 Spring 应用上下文找到所有的 {@link ServletContextInitializer}
     * 也就会找到各种 {@link RegistrationBean},然后依次调用他们的 `onStartup` 方法,向 ServletContext 上下文注册 Servlet、Filter 和 EventListener
     * 例如 {@link DispatcherServletAutoConfiguration} 中的 {@link DispatcherServletRegistrationBean} 就会注册 {@link DispatcherServlet} 对象
     * 这也就是我们熟知的 Spring MVC 的核心组件,关于它可参考我的 [精尽Spring MVC源码分析 - 文章导读](https://www.cnblogs.com/lifullmoon/p/14123963.html) 文章
     * 所以这里执行完了,也就启动了 Tomcat,同时注册了所有的 Servlet,那么 Web 应用准备就绪了
     */
    for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
        beans.onStartup(servletContext);
    }
}

过程如下:

  1. 将当前 Spring 应用上下文设置到 ServletContext 上下文的属性中,同时将 ServletContext 上下文设置到 Spring 应用上下文中

  2. 向 Spring 应用上下文注册一个 ServletContextScope 对象(ServletContext 的封装)

  3. 向 Spring 应用上下文注册 contextParameterscontextAttributes 属性(会先被封装成 Map)

  4. 【重点】调用 getServletContextInitializerBeans() 方法,先从 Spring 应用上下文找到所有的 ServletContextInitializer 对象,也就会找到各种 RegistrationBean,然后依次调用他们的 onStartup 方法,向 ServletContext 上下文注册 Servlet、Filter 和 EventListener

    protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
        return new ServletContextInitializerBeans(getBeanFactory());
    }
    

重点在于上面的第 4 步,创建了一个 ServletContextInitializerBeans 对象,实现了 Collection 集合接口,所以可以遍历

它会找到所有的 RegistrationBean(实现了 ServletContextInitializer 接口),然后调用他们的 onStartup(ServletContext) 方法,也就会往 ServletContext 中添加他们对应的 Servlet 或 Filter 或 EventListener 对象,这个方法比较简单,在后面讲到的 RegistrationBean 小节中会提到

继续往下看

ServletContextInitializerBeans

org.springframework.boot.web.servlet.ServletContextInitializerBeans,对 ServletContextInitializer 实现类的封装,会找到所有的 ServletContextInitializer 实现类

public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {

	private static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";

	/**
	 * Seen bean instances or bean names.
	 * 所有的 Servlet or Filter or EventListener or ServletContextInitializer 对象
	 * 也可能是该对象对应的 `beanName`
	 */
	private final Set<Object> seen = new HashSet<>();

	/**
	 * 保存不同类型的 ServletContextInitializer 对象
	 * key:Servlet or Filter or EventListener or ServletContextInitializer
	 * value:ServletContextInitializer 实现类
	 */
	private final MultiValueMap<Class<?>, ServletContextInitializer> initializers;

	/**
	 * 指定 ServletContextInitializer 的类型,默认就是它
	 */
	private final List<Class<? extends ServletContextInitializer>> initializerTypes;

	/**
	 * 排序后的所有 `initializers` 中的 ServletContextInitializer 实现类(不可被修改)
	 */
	private List<ServletContextInitializer> sortedList;
    
    @SafeVarargs
	public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
			Class<? extends ServletContextInitializer>... initializerTypes) {
		this.initializers = new LinkedMultiValueMap<>();
		// <1> 设置类型为 `ServletContextInitializer`
		this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
				: Collections.singletonList(ServletContextInitializer.class);
		// <2> 找到 IoC 容器中所有 `ServletContextInitializer` 类型的 Bean
		// 并将这些信息添加到 `seen` 和 `initializers` 集合中
		addServletContextInitializerBeans(beanFactory);
		// <3> 从 IoC 容器中获取 Servlet or Filter or EventListener 类型的 Bean
		// 适配成 RegistrationBean 对象,并添加到 `initializers` 和 `seen` 集合中
		addAdaptableBeans(beanFactory);
		// <4> 将 `initializers` 中的所有 ServletContextInitializer 进行排序,并保存至 `sortedList` 集合中
		List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
				.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
				.collect(Collectors.toList());
		this.sortedList = Collections.unmodifiableList(sortedInitializers);
		// <5> DEBUG 模式下打印日志
		logMappings(this.initializers);
	}
}

过程如下:

  1. 设置类型为 ServletContextInitializer
  2. 找到 IoC 容器中所有 ServletContextInitializer 类型的 Bean,并将这些信息添加到 seeninitializers 集合中
  3. 从 IoC 容器中获取 Servlet or Filter or EventListener 类型的 Bean,适配成 RegistrationBean 对象,并添加到 initializersseen 集合中
  4. initializers 中的所有 ServletContextInitializer 进行排序,并保存至 sortedList 集合中
  5. DEBUG 模式下打印日志

比较简单,这里就不继续往下分析源码了,感兴趣可以看一看 ServletContextInitializerBeans.java

这里你要知道 RegistrationBean 实现了 ServletContextInitializer 接口,我们的 Spring Boot 应用如果要添加 Servlet 或者 Filter,可以注入一个 ServletRegistrationBean<T extends Servlet> 或者 FilterRegistrationBean<T extends Filter> 类型的 Bean

RegistrationBean

org.springframework.boot.web.servlet.RegistrationBean,基于 Servlet 3.0+,往 ServletContext 注册 Servlet、Filter 和 EventListener

public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
	@Override
	public final void onStartup(ServletContext servletContext) throws ServletException {
		// 抽象方法,交由子类实现
		String description = getDescription();
		// 抽象方法,交由子类实现
		register(description, servletContext);
	}
}

类图:

DynamicRegistrationBean

public abstract class DynamicRegistrationBean<D extends Registration.Dynamic> extends RegistrationBean {
	@Override
	protected final void register(String description, ServletContext servletContext) {
		// 抽象方法,交由子类实现
		D registration = addRegistration(description, servletContext);
		// 设置初始化参数,也就是设置 `Map<String, String> initParameters` 参数
		configure(registration);
	}
    
    protected void configure(D registration) {
		registration.setAsyncSupported(this.asyncSupported);
		if (!this.initParameters.isEmpty()) {
			registration.setInitParameters(this.initParameters);
		}
	}
}

ServletRegistrationBean

public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
    
	@Override
	protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
		// 获取 Servlet 的名称
		String name = getServletName();
		// 将该 Servlet 添加至 ServletContext 上下文中
		return servletContext.addServlet(name, this.servlet);
	}

	@Override
	protected void configure(ServletRegistration.Dynamic registration) {
		super.configure(registration);
		// 设置需要拦截的 URL,默认 `/*`
		String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
		if (urlMapping.length == 0 && this.alwaysMapUrl) {
			urlMapping = DEFAULT_MAPPINGS;
		}
		if (!ObjectUtils.isEmpty(urlMapping)) {
			registration.addMapping(urlMapping);
		}
		// 设置需要加载的优先级
		registration.setLoadOnStartup(this.loadOnStartup);
		if (this.multipartConfig != null) {
			registration.setMultipartConfig(this.multipartConfig);
		}
	}
}

DispatcherServletRegistrationBean

public class DispatcherServletRegistrationBean extends ServletRegistrationBean<DispatcherServlet>
		implements DispatcherServletPath {

	private final String path;

	/**
	 * Create a new {@link DispatcherServletRegistrationBean} instance for the given
	 * servlet and path.
	 * @param servlet the dispatcher servlet
	 * @param path the dispatcher servlet path
	 */
	public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
		super(servlet);
		Assert.notNull(path, "Path must not be null");
		this.path = path;
		super.addUrlMappings(getServletUrlMapping());
	}

	@Override
	public String getPath() {
		return this.path;
	}

	@Override
	public void setUrlMappings(Collection<String> urlMappings) {
		throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
	}

	@Override
	public void addUrlMappings(String... urlMappings) {
		throw new UnsupportedOperationException("URL Mapping cannot be changed on a DispatcherServlet registration");
	}
}

3. finishRefresh 方法

// // ServletWebServerApplicationContext.java
@Override
protected void finishRefresh() {
    // 调用父类方法,会发布 ContextRefreshedEvent 上下文刷新事件
    super.finishRefresh();
    /**
     * 启动上面 {@link #onRefresh }创建的 WebServer,上面仅启动 {@link Tomcat} 容器,Servlet 添加到了 ServletContext 上下文中
     * 这里启动 {@link TomcatWebServer} 容器对象,对每一个 TomcatEmbeddedContext 中的 Servlet 进行加载并初始化
     */
    WebServer webServer = startWebServer();
    if (webServer != null) {
        publishEvent(new ServletWebServerInitializedEvent(webServer, this));
    }
}

首先会调用父类方法,会发布 ContextRefreshedEvent 上下文刷新事件,然后调用自己的 startWebServer() 方法,启动上面 2. onRefresh 方法 创建的 WebServer

因为上面仅启动 Tomcat 容器,Servlet 添加到了 ServletContext 上下文中,这里启动 TomcatWebServer 容器对象,会对每一个 TomcatEmbeddedContext 中的 Servlet 进行加载并初始化,如下:

private WebServer startWebServer() {
    WebServer webServer = this.webServer;
    if (webServer != null) {
        webServer.start();
    }
    return webServer;
}

TomcatWebServer

org.springframework.boot.web.embedded.tomcat.TomcatWebServer,对 Tomcat 的封装,用于控制 Tomcat 服务器

3.1 start 方法

start() 方法,启动 TomcatWebServer 服务器,初始化前面已添加的 Servlet 对象们

@Override
public void start() throws WebServerException {
    // 加锁启动
    synchronized (this.monitor) {
        // 已启动则跳过
        if (this.started) {
            return;
        }
        try {
            addPreviouslyRemovedConnectors();
            Connector connector = this.tomcat.getConnector();
            if (connector != null && this.autoStart) {
                /**
                 * 对每一个 TomcatEmbeddedContext 中的 Servlet 进行加载并初始化,先找到容器中所有的 {@link org.apache.catalina.Wrapper}
                 * 它是对 {@link javax.servlet.Servlet} 的封装,依次加载并初始化它们
                 */
                performDeferredLoadOnStartup();
            }
            checkThatConnectorsHaveStarted();
            this.started = true;
            logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
                    + getContextPath() + "'");
        } catch (ConnectorStartFailedException ex) {
            stopSilently();
            throw ex;
        } catch (Exception ex) {
            PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort());
            throw new WebServerException("Unable to start embedded Tomcat server", ex);
        } finally {
            Context context = findContext();
            ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
        }
    }
}

加锁启动,已启动则跳过

关键在于 performDeferredLoadOnStartup() 这个方法,对每一个 TomcatEmbeddedContext 中的 Servlet 进行加载并初始化,先找到容器中所有的 org.apache.catalina.Wrapper,它是对 javax.servlet.Servlet 的封装,依次加载并初始化它们

private void performDeferredLoadOnStartup() {
    try {
        for (Container child : this.tomcat.getHost().findChildren()) {
            if (child instanceof TomcatEmbeddedContext) {
                /**
                 * 找到容器中所有的 {@link org.apache.catalina.Wrapper},它是对 {@link javax.servlet.Servlet} 的封装
                 * 那么这里将依次加载并初始化它们
                 */
                ((TomcatEmbeddedContext) child).deferredLoadOnStartup();
            }
        }
    } catch (Exception ex) {
        if (ex instanceof WebServerException) {
            throw (WebServerException) ex;
        }
        throw new WebServerException("Unable to start embedded Tomcat connectors", ex);
    }
}

好了,到这里 Spring Boot 内嵌的 Tomcat 容器差不多准备就绪了,继续往下追究就涉及到 Tomcat 底层的东西了,所以这里点到为止

总结

本文分析了 Spring Boot 内嵌 Tomcat 容器的实现,主要是 Spring Boot 的 Spring 应用上下文(ServletWebServerApplicationContext)在 refresh() 刷新阶段进行了扩展,分别在 onRefresh()finishRefresh() 两个地方,可以跳到前面的 回顾 小节中看看,分别做了以下事情:

  1. 创建一个 WebServer 服务对象,例如 TomcatWebServer 对象,对 Tomcat 的封装,用于控制 Tomcat 服务器
    1. 先创建一个 org.apache.catalina.startup.Tomcat 对象 tomcat,使用临时目录作为基础目录(tomcat.端口号),退出时删除,同时会设置端口、编码、最小空闲线程和最大线程数
    2. tomcat 创建一个 TomcatEmbeddedContext 上下文对象,会添加一个 TomcatStarter(实现 javax.servlet.ServletContainerInitializer 接口)到这个上下文对象中
    3. tomcat 封装到 TomcatWebServer 对象中,实例化过程会启动 tomcat,启动后会触发 javax.servlet.ServletContainerInitializer 实现类的回调,也就会触发 TomcatStarter 的回调,在其内部会调用 Spring Boot 自己的 ServletContextInitializer 初始器,例如 ServletWebServerApplicationContext#selfInitialize(ServletContext) 匿名方法
    4. 在这个匿名方法中会找到所有的 RegistrationBean,执行他们的 onStartup 方法,将其关联的 Servlet、Filter 和 EventListener 添加至 Servlet 上下文中,包括 Spring MVC 的 DispatcherServlet 对象
  2. 启动上一步创建的 TomcatWebServer 对象,上面仅启动 Tomcat 容器,Servlet 添加到了 ServletContext 上下文中,这里会将这些 Servlet 进行加载并初始化

这样一来就完成 Spring Boot 内嵌的 Tomcat 就启动完成了,关于 Spring MVC 相关内容可查看 《精尽 Spring MVC 源码分析 - 文章导读》 这篇文章。

ServletContainerInitializer 也是 Servlet 3.0 新增的一个接口,容器在启动时使用 JAR 服务 API(JAR Service API) 来发现 ServletContainerInitializer 的实现类,并且容器将 WEB-INF/lib 目录下 JAR 包中的类都交给该类的 onStartup() 方法处理,我们通常需要在该实现类上使用 @HandlesTypes 注解来指定希望被处理的类,过滤掉不希望给 onStartup() 处理的类。

你是否有一个疑问,Spring Boot 不也是支持打成 war 包,然后放入外部的 Tomcat 容器运行,这种方式的实现在哪里呢?我们在下一篇文章进行分析

posted @ 2021-07-01 17:15  月圆吖  阅读(1955)  评论(0编辑  收藏  举报