开始
- spring initilizer:根据依赖构建工具、springboot 版本等生成 Java 工程。
- 手把手教你手写一个最简单的 Spring Boot Starter
- Starter 命名规则
Spring 官方定义的 Starter 通常命名遵循的格式为 spring-boot-starter-{name},例如 spring-boot-starter-data-mongodb。Spring 官方建议,非官方 Starter 命名应遵循 {name}-spring-boot-starter 的格式,例如,myjson-spring-boot-starter。- Starter 提供了以下功能
整合了模块需要的所有依赖,统一集合到 Starter 中。
提供了默认配置,并允许我们调整这些默认配置。
提供了自动配置类对模块内的 Bean 进行自动装配,注入 Spring 容器中。- SpringBoot 项目启动时,类加载器会从 META-INF/spring.factories 加载给定类型的工厂实现的完全限定类名。也就是说类加载器得到工程中所有 jar 包中的 META-INF/spring.factories 文件资源,从而得到了一些包括自动配置相关的类的集合,然后将它们实例化,放入 Spring 容器中。
源码分析
入口程序
@SpringBootApplication
public class SpringDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDemoApplication.class, args);
}
}
SpringApplication.run 根据入参 SpringDemoApplication 找到当前 SpringBoot 应用的配置类(即标注了 @SpringBootApplication 的类)
SpringFactoriesLoader
Java SPI VS Spring SPI
自动装配如何实现
todo:注解上的注解 Spring 或 Java 是怎么解析的?
从 @SpringBootApplication 入手
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {//...}
其中 @EnableAutoConfiguration 的实现 AutoConfigurationImportSelector,会从 /META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件下读取自动装配的配置类
// From org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
// 从 /META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 配置文件中读取自动装配的类
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
// From org.springframework.boot.context.annotation.ImportCandidates#load
private static final String LOCATION = "META-INF/spring/%s.imports";
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = String.format(LOCATION, annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
List<String> importCandidates = new ArrayList<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}
从自动装配类主要有:
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.netty.NettyAutoConfiguration
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration
// SpringMVC 相关配置
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
SpringBoot 启动过程
ServletWebServerApplicationContext 是 SpringBoot 「Web应用」对应的容器,其重写了 onRefresh(), 在 Spring 刷新容器时,会回调该方法。
ServletWebServerApplicationContext#onRresh() :
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
StartupStep createWebServer = this.getApplicationStartup().start("spring.boot.webserver.create");
ServletWebServerFactory factory = getWebServerFactory();
createWebServer.tag("factory", factory.getClass().toString());
// 注册
this.webServer = factory.getWebServer(getSelfInitializer());
createWebServer.end();
// 注册优雅关闭 Servlet 容器的回调
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
// 注册 启动 Servlet 容器,以及在容器 ready 后发送 ServletWebServerInitializedEvent 的回调
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
该方法完成以下几个关键行为:
- 创建 ServletWebServerFactory 对象,并放入 Spring 容器中,并据此创建并启动 Servlet 容器;
- ServletWebServerFactory 在 ServletWebServerFactoryAutoConfiguration、ServletWebServerFactoryConfiguration 配置类中被注入到 Spring 容器
// From ServletWebServerFactoryAutoConfiguration.java
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, // 注入相关的 BeanPostProcessor
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
// From ServletWebServerFactoryConfiguration.java
@Configuration(proxyBeanMethods = false)
// 在类路径中包含了「指定类:Tomcat.class 等」时,向 Spring 容器中注入 TomcatServletWebServerFactory
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
// From WebServerFactoryCustomizerBeanPostProcessor.java
private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
LambdaSafe.callbacks(WebServerFactoryCustomizer.class, getCustomizers(), webServerFactory)
.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
// 将 ServerProperties 中配置的 Servlet 容器属性设置到 WebServerFactory 中
.invoke((customizer) -> customizer.customize(webServerFactory));
}
- 注册回调:在 Servlet 容器创建完成后,为 ServletWebServerApplicationContext 容器设置 serverContext;
- 注册回调:启动 Web 应用(webServer.start()),并发送 ServletWebServerInitializedEvent 事件;
- 注册回调:在 Spring 容器销毁后,优雅关闭 Servlet 容器;
ConfigurationProperties
Annotation for externalized configuration. Add this to a class definition or a {@code @Bean} method in a {@code @Configuration} class if you want to bind and validate some external Properties (e.g. from a .properties file).
todo:原理是啥?
SpringBoot @ConfigurationProperties详解
使用: 「@ConfigurationProperties + @Configuration 组合」 或 @EnableConfigurationProperties
ObjectProvider
A variant of {@link ObjectFactory} designed specifically for injection points, allowing for programmatic optionality and lenient not-unique handling.
ObjectProvider使用说明
如果待注入参数的Bean为空或有多个时,便是 ObjectProvider 发挥作用的时候了。
- 如果注入实例为空时,使用 ObjectProvider 则避免了强依赖导致的依赖对象不存在异常;
- 如果有多个实例,ObjectProvider 的方法会根据 Bean 实现的 Ordered 接口或 @Order 注解指定的先后顺序获取一个 Bean。从而了提供了一个更加宽松的依赖注入方式。ObjectProvider 实现了 Iterable 接口,即也可以同时注入多个实例
目前 Spring 主要在 org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency 方法中使用了它
应用
// From ServletWebServerFactoryConfiguration.java
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
可以看出 TomcatServletWebServerFactory 是通过 ObjectProvider 注入相关依赖的,在容器中没有相关的 Bean 对象时也能正常运行,而且使得程序有很好的扩展性,即程序员可以通过实现接口 TomcatConnectorCustomizer、TomcatContextCustomizer、TomcatProtocolHandlerCustomizer ,并手动创建其 Bean 对象来对创建出的 TomcatServer 对象施加影响。
Servlet 组件注入
ServletContainerInitializer
- Tomcat 是怎样处理 SpringBoot应用的?
在Servlet 3.0 之后,可以通过 ServletContainerInitializer 动态向 Tomcat 中新增 Servlet、 Filter,这样 Tomcat 就不必强依赖 web.xml。
除了以 Jar 的形式直接执行 Main 方法外, Spring Boot 还支持将 Boot 应用打包成 War 文件,部署到标准容器中,不使用 Embedded 容器。相比执行 main 方法(SpringApplication.run(SpringDemoApplication.class, args);)来启动 Spring Boot 应用,以 Web 应用提供时, Boot 的能力是如何提供的呢?
Tomcat 启动时会依次遍历通过 SPI 提供的 ServletContainerInitializer 实现类,首先解析 @HandlesTypes 得到其属性 value 值对应类的所有实现类(解析过程由 Servlet容器 提供支持:利用字节码扫描框架(例如ASM、BCEL)从classpath中扫描出来),然后传递给 ServletContainerInitializer#onStartup 方法。
在 TomcatServletWebServerFactory#configureContext 中,向 Tomcat 代表当前 Web应用程序的容器组件添加了 ServletContainerInitializer(TomcatStarter)。TomcatStarter#onStartUp 中遍历 ServletContextInitializer 数组,将相关 Servlet 组件 Servlet(DispatcherServlet)、Filter、Listener 注入到 Tomcat 中。
protected void configureContext(Context context, ServletContextInitializer[] initializers) {
// TomcatStarter 实现了 ServletContainerInitializer 接口
TomcatStarter starter = new TomcatStarter(initializers);
if (context instanceof TomcatEmbeddedContext) {
// 代表当前 Web应用程序(Tomcat 容器组件:Server -> Service -> Engine -> Host -> Context -> Wrapper(Servlet))
TomcatEmbeddedContext embeddedContext = (TomcatEmbeddedContext) context;
embeddedContext.setStarter(starter);
embeddedContext.setFailCtxIfServletStartFails(true);
}
// 将 ServletContainerInitializer 添加到 Web 应用程序中
context.addServletContainerInitializer(starter, NO_CLASSES);
// 省略后面的代码 ...
}
然后在 TomcatWebServer#initialize 会启动 tomcat,触发初始化过程(此时就会触发 TomcatStarter#onStartUp 方法的执行),向 Tomcat 中注入Servlet 组件。
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
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)
this.tomcat.start();
/*
省略后面的代码 ...
*/
}
}
}
SpringBoot 中的 TomcatStarter(ServletContainerInitializer)#onStartUp 调用栈:
Q1:这里已经通过 tomcat.start() 启动了 Tomcat(Spring容器刷新 onRefresh 阶段),TomcatWebServer#start (Spring容器刷新 finishRefresh 阶段)作用是什么?
A1:Tomcat.start() 触发 TomcatStarter#onStartUp 的执行完成的是:
- 将 Servlet、Listener、Filter 添加到 ServletContext 中。
// From RegistrationBean#onStartup
public final void onStartup(ServletContext servletContext) throws ServletException {
String description = getDescription();
if (!isEnabled()) {
logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");
return;
}
register(description, servletContext);
}
// From ServletRegistrationBean#addRegistration
protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
String name = getServletName();
// 将 Servlet 添加到 ServletContext 中
return servletContext.addServlet(name, this.servlet);
}
// From ServletRegistrationBean#configure
// 完成 path 到 servlet 的映射
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
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);
}
}
DispatcherServlet 作为 ServletRegistrationBean 的子类,通过 SpringBoot 的自动化配置类 DispatcherServletRegistrationConfiguration 中被自动注入。
// 作为一个配置类,不进行 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;
}
}
}
所以, 通过 SpringBoot 注入的 Servlet 默认为 DispatcherServlet
- 将 ServletWebServerApplicationContext (Spring 容器)注入到 ServletContext (Servlet 容器)中;同时也将 ServletContext 注入到 ServletWebServerApplicationContext 中。
// From ServletWebServerApplicationContext.java
protected void prepareWebApplicationContext(ServletContext servletContext) {
Object rootContext = servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (rootContext != null) {
if (rootContext == this) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - "
+ "check whether you have multiple ServletContextInitializers!");
}
return;
}
servletContext.log("Initializing Spring embedded WebApplicationContext");
try {
// 将 ServletWebServerApplicationContext 注入到 ServletContext
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this);
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name ["
+ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
// 将 ServletContext 注入到 ServletWebServerApplicationContext 中
setServletContext(servletContext);
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - getStartupDate();
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
在 ServletWebServerApplicationContext#createWebServer 方法中,向 Spring 容器中注入了 WebServerStartStopLifecycle 实例,即在容器中的所有 Bean 完全创建成功后,才会调用 TomcatWebServer#start 真正完成 Tomcat 的启动(TomcatWebServer#performDeferredLoadOnStartup 完成容器组件 Wrapper 的加载:执行 org.apache.catalina.core.StandardWrapper#initServlet,即执行 Servlet 的 init 方法。)
private void createWebServer() {
WebServer webServer = this.webServer;
// 省略代码...
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
// 省略代码...
}
// 实现了 SmartLifecycle 接口,会在 Spring finishRefresh 阶段调用 start()
class WebServerStartStopLifecycle implements SmartLifecycle {
private final ServletWebServerApplicationContext applicationContext;
private final WebServer webServer;
private volatile boolean running;
WebServerStartStopLifecycle(ServletWebServerApplicationContext applicationContext, WebServer webServer) {
this.applicationContext = applicationContext;
this.webServer = webServer;
}
@Override
public void start() {
this.webServer.start();
this.running = true;
this.applicationContext
.publishEvent(new ServletWebServerInitializedEvent(this.webServer, this.applicationContext));
}
// 省略代码...
}
注意:Wrapper 的 loadOnStartUp >= 0 时才会在 Tomcat 启动完成就立即执行 Wrapper 里面的 Servlet#init 方法,但 SpringBoot 向 Tomcat 注入 DispatcherServlet 时,loadOnStartUp = -1 (servlet.WebMvcProperties.Servlet#loadOnStartup 默认值为 -1),所以在初次访问时,才会执行 DispatcherServlet#init 方法。在 SpringBoot 完成启动后,就调用 DispatcherServlet#init 的设置方法:将spring.mvc.servlet.load-on-startup 设置成 >= 0 的正整数。
profile / environment
使用
- pom.xml
<project>
<profiles>
<profile>
<id>dev</id>
<properties>
<!-- 用于替换 application.yml 中的变量 -->
<spring.profiles.active>dev</spring.profiles.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<modules>
<module>moudle-1</module>
<module>moudle-2</module>
<module>moudle-3</module>
</modules>
</profile>
<profile>
<id>prod</id>
<modules>
<module>moudle-1</module>
<module>moudle-2</module>
<module>moudle-3</module>
<module>moudle-4</module>
</modules>
<properties>
<!-- 用于替换 application.yml 中的变量 -->
<spring.profiles.active>prod</spring.profiles.active>
</properties>
</profile>
</project>
- application.yml
spring:
profiles:
active: @spring.profiles.active@
- swagger
/*
只有在 dev 环境下,才会向 Spring 容器中注入 SwaggerConfig 配置
http://localhost:8080/swagger-ui.html
*/
@Profile({"dev"})
@EnableSwagger2
@Configuration
public class SwaggerConfig {
@Bean
public UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.deepLinking(true)
.displayOperationId(false)
// 隐藏UI上的Models模块
.defaultModelsExpandDepth(-1)
.defaultModelExpandDepth(0)
.defaultModelRendering(ModelRendering.EXAMPLE)
.displayRequestDuration(false)
.docExpansion(DocExpansion.NONE)
.filter(false)
.maxDisplayedTags(null)
.operationsSorter(OperationsSorter.ALPHA)
.showExtensions(false)
.tagsSorter(TagsSorter.ALPHA)
.validatorUrl(null)
.build();
}
@Bean
public Docket createApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// 比如:com.demo.controller
.apis(RequestHandlerSelectors.basePackage("projectFullDir"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("项目接口文档")
.version("1.0")
.build();
}
}
Spring Profiles
Consider a basic scenario: We have a bean that should only be active during development but not deployed in production.We annotate that bean with a dev profile(such as @Profile(“dev”)), and it will only be present in the container during development. In production, the dev simply won’t be active. As a quick side note, profile names can also be prefixed with a NOT operator, e.g., !dev, to exclude them from a profile.
Threre are variety of ways to activate and set the profiles:WebApplicationInitializer、ConfigurableEnvironment、web.xml、JVM System Parameter、Unix Environment Variable、Maven Profile
mvn clean package -Pprod:This command will package the application for the prod profile. It also applies the spring.profiles.active value prod for this application when it is running.
其它
- Spring Boot 源码分析 - 内嵌Tomcat容器的实现
- Spring Boot 2.1.6.RELEASE embed tomcat启动过程
TomcatEmbeddedContext (StandardContext 子类) 就是tomcat的child容器,里面有servletContext
- tomcat:LifecycleListener
- Spring Boot 2.1.6.RELEASE embed tomcat启动过程