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 mvc
和 tomcat
相关依赖,关于 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();
}
过程如下:
-
获取当前
WebServer
容器对象,首次进来为空 -
获取
ServletContext
上下文对象@Override @Nullable public ServletContext getServletContext() { return this.servletContext; }
-
如果
WebServer
和ServletContext
都为空,则需要创建一个,此时使用 Spring Boot 内嵌 Tomcat 容器则会进入该分支-
获取 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.*
相关的配置设置到该对象中,这一步不深入分析,感兴趣可以去看一看 -
先创建一个
ServletContextInitializer
Servlet 上下文初始器,实现也就是当前类的this#selfInitialize(ServletContext)
方法,如下:private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() { return this::selfInitialize; }
这个
ServletContextInitializer
在后面会被调用,请记住这个方法 -
从
factory
工厂中创建一个WebServer
容器对象,例如创建一个TomcatWebServer
容器对象,并初始化ServletContext
上下文,该过程会创建一个Tomcat
容器并启动,启动过程异步触发了TomcatStarter#onStartup
方法,也就会调用第2
步的ServletContextInitializer#selfInitialize(ServletContext)
方法
-
-
否则,如果
ServletContext
不为空,说明使用了外部的 Servlet 容器(例如 Tomcat)- 那么这里主动调用
this#selfInitialize(ServletContext)
方法来注册各种 Servlet、Filter
- 那么这里主动调用
-
将 ServletContext 的一些初始化参数关联到当前 Spring 应用的 Environment 环境中
整个过程有点绕,如果获取到的 WebServer
和 ServletContext
都为空,说明需要使用内嵌的 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);
}
过程如下:
-
禁用 MBean 注册中心
-
创建一个 Tomcat 对象
tomcat
-
创建一个临时目录(退出时删除)
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); } }
-
将这个临时目录作为 Tomcat 的目录
-
创建一个 NIO 协议的 Connector 连接器对象,并添加到第
2
步创建的tomcat
中 -
对 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); } }
-
禁止自动部署
-
同时支持多个 Connector 连接器(默认没有)
-
调用
prepareContext(..)
方法,创建一个TomcatEmbeddedContext
上下文对象,并进行初始化工作,配置TomcatStarter
作为启动器,会将这个上下文对象设置到当前tomcat
中去 -
调用
getTomcatWebServer(Tomcat)
方法,创建一个TomcatWebServer
容器对象,是对tomcat
的封装,用于控制 Tomcat 服务器
整个 Tomcat 的初始化过程没有特别的复杂,主要是因为这里没有深入分析,我们知道大致的流程即可,这里我们重点关注第 9
和 10
步,接下来依次分析
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);
}
整个过程我们挑主要的流程来看:
-
创建一个 TomcatEmbeddedContext 上下文对象
context
,接下来进行一系列的配置 -
设置
context-path
-
设置 Tomcat 根目录
-
注册默认的 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"); }
-
将这个
context
上下文对象添加到tomcat
中去 -
调用
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);
}
}
配置过程如下:
-
创建一个
TomcatStarter
启动器,此时把ServletContextInitializer
数组传入进去了,并设置到context
上下文中 -
设置错误页面
-
配置
context
上下文的 Session 会话,例如超时会话时间 -
对
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.ServletContainerInitializer
的 onStartup(..)
方法
在上面的 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);
}
}
过程如下:
-
将当前 Spring 应用上下文设置到 ServletContext 上下文的属性中,同时将 ServletContext 上下文设置到 Spring 应用上下文中
-
向 Spring 应用上下文注册一个 ServletContextScope 对象(ServletContext 的封装)
-
向 Spring 应用上下文注册
contextParameters
和contextAttributes
属性(会先被封装成 Map) -
【重点】调用
getServletContextInitializerBeans()
方法,先从 Spring 应用上下文找到所有的ServletContextInitializer
对象,也就会找到各种 RegistrationBean,然后依次调用他们的onStartup
方法,向 ServletContext 上下文注册 Servlet、Filter 和 EventListenerprotected 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);
}
}
过程如下:
- 设置类型为
ServletContextInitializer
- 找到 IoC 容器中所有
ServletContextInitializer
类型的 Bean,并将这些信息添加到seen
和initializers
集合中 - 从 IoC 容器中获取 Servlet or Filter or EventListener 类型的 Bean,适配成
RegistrationBean
对象,并添加到initializers
和seen
集合中 - 将
initializers
中的所有ServletContextInitializer
进行排序,并保存至sortedList
集合中 - 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()
两个地方,可以跳到前面的 回顾 小节中看看,分别做了以下事情:
- 创建一个 WebServer 服务对象,例如 TomcatWebServer 对象,对 Tomcat 的封装,用于控制 Tomcat 服务器
- 先创建一个
org.apache.catalina.startup.Tomcat
对象tomcat
,使用临时目录作为基础目录(tomcat.端口号
),退出时删除,同时会设置端口、编码、最小空闲线程和最大线程数 - 为
tomcat
创建一个TomcatEmbeddedContext
上下文对象,会添加一个TomcatStarter
(实现javax.servlet.ServletContainerInitializer
接口)到这个上下文对象中 - 将
tomcat
封装到 TomcatWebServer 对象中,实例化过程会启动tomcat
,启动后会触发javax.servlet.ServletContainerInitializer
实现类的回调,也就会触发TomcatStarter
的回调,在其内部会调用 Spring Boot 自己的ServletContextInitializer
初始器,例如ServletWebServerApplicationContext#selfInitialize(ServletContext)
匿名方法 - 在这个匿名方法中会找到所有的
RegistrationBean
,执行他们的onStartup
方法,将其关联的 Servlet、Filter 和 EventListener 添加至 Servlet 上下文中,包括 Spring MVC 的 DispatcherServlet 对象
- 先创建一个
- 启动上一步创建的 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 容器运行,这种方式的实现在哪里呢?我们在下一篇文章进行分析