深入springboot怎么启动tomcat
这是中高级工程师面试中常问的问题。
知道现在有多卷了吧!
我记得我刚找工作那会儿,我只要8000的工资,面试官都要问这个问题。我真TM的醉了!
关于SpringBoot自动配置流程请看:深入Springboot启动流程+自动配置原理.
如果你对基本的启动原理有大致的了解,那么可以继续阅读此篇文章。否则请先阅读深入Springboot启动流程+自动配置原理.。
@EnableAutoConfiguration做了哪些事
我们知道,因为@EnableAutoConfiguration
注解的存在,SpringBoot项目启动的时候,会去找到依赖包中META-INF/spring.factories文件,将文件中的自动配置类找出来,加载进内存!
那么和Tomcat相关的配置类,也存在于spring.factories之中:
我们发现这里配置了一个类: ServletWebServerFactoryAutoConfiguration。
这个类是干嘛用的呢?
点进去一看!
不看不知道,一看吓一尿:
根据上图的标识我们分三步来解释:
-
@ConditionalOnWebApplication
( type = Type.SERVLET):条件判断,如果当前的项目的类型为Servlet我才继续运行当前的ServletWebServerFactoryAutoConfiguration配置类,这里判断通过。 -
@EnableConfigurationProperties
({ServerProperties.class}),这个注解我在深入Springboot启动流程+自动配置原理.中有解释。那么呢,这里呢,就是去绑定我们application.properties中关于server的参数!
例如:
指定我们服务的端口、地址等。在SpringBoot启动的时候就会被从application.properties中读取到当前的ServletWebServerFactoryAutoConfiguration配置类中,进行web服务的初始化! -
@Import
(EmbeddedTomcat.class、 EmbeddedJetty.class): 这是什么? Tomcat?Jetty?并且通过@Import
注解注入容器了!聪明的朋友猜到了,Tomcat/Jetty服务就是在这里进行初始化的。
好!由于我们这里研究的是TomCat,我们点进EmbeddedTomcat看看!
可以看到EmbeddedTomcat是一个由@Configuration
修饰的静态内部配置类,向容器中注入了一个名叫TomcatServletWebServerFactory的对象(重点,后面会用到这个对象)。
这个时候有同学就有疑问了,在ServletWebServerFactoryAutoConfiguration类中通过@Import
导入了三个对象:
- EmbeddedTomcat
- EmbeddedJetty
- EmbeddedUndertow
那这里为什么只加载了EmbeddedTomcat呢? 因为我们的@ConditionalOnClass
条件注解,我们的依赖中没有Jetty和Undertow相关的类,因此EmbeddedJetty和EmbeddedUndertow不会加载。只会加载EmbeddedTomcat!
所以现在呢,重点来到了TomcatServletWebServerFactory这个对象上,我们进去看看。
我们发现通过new TomcatServletWebServerFactory(); 创建了一个TomcatServletWebServerFactory对象,然后构造方法设置了一系列参数。这里我们直接跳过,去看这个类中最重要的一个方法:getWebServer()
这个方法我们后面会做解释,划重点!!!!!
小总结
那么@EnableAutoConfiguration
对于Tomcat的工作现在就做完了,做了什么事情呢?
向Spring容器中注入了一个初始化后,名叫TomcatServletWebServerFactory的对象。该对象继承自ServletWebServerFactory接口(记住)。我们现在可以认为TomCat服务准备好了,等待启动。那什么时候启动的呢?
Tomcat何时启动的呢?
这个时候就要回到我们的启动类上了:
这个SpringApplication.run()方法我们之前一直没有系统介绍过,这里我简单介绍一下。
首先我们点进run方法
发现首先创建了一个SpringApplication对象。
然后继续调用到了一个同名的run方法头上。
好!
重点就是这个run方法了,它做了什么事情呢?
我这里借鉴一下百度百科上面的解释:
public ConfigurableApplicationContext run(String... args) {
//记录程序运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// ConfigurableApplicationContext Spring 的上下文
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//【1、获取并启动监听器】
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//【2、构造应用上下文环境】
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//处理需要忽略的Bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
///【3、初始化应用上下文】
context = createApplicationContext();
//实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//【4、刷新应用上下文前的准备阶段】
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//【5、刷新应用上下文】
refreshContext(context);
//【6、刷新应用上下文后的扩展接口】
afterRefresh(context, applicationArguments);
//时间记录停止
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//发布容器启动完成事件
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
- 获取并启动监听器 通过加载META-INF/spring.factories 完成了 SpringApplicationRunListener实例化工作(告诉相关人员,SpringBoot要启动了,你们把自己该初始化的初始化了)
- 构造容器环境,简而言之就是加载系统变量,环境变量,配置文件
- 创建容器
- 实例化SpringBootExceptionReporter.class,用来支持报告关于启动的错误
- 准备容器
- 刷新容器 :refreshContext(context)
- 刷新容器后的扩展接口
由于我们这里专门讨论TomCat的启动,所以我们其他的就不多去研究,直接找到最重要的地方:
第6步:刷新容器 ,这一步是整个SpringBoot非常重要的一步,IOC等等核心都是通过这一步来实现的。
我们进入refreshContext()方法看看:
好,继续点:
继续:
发现是一个接口,我们看有那些实现类:
哇,有那么多实现类,到底调用的是哪个呢?
java多态的知识来了
这个时候我们去看看,这个方法是谁调用的?
是一个叫ConfigurableApplicationContext的类调用的,那么继续去寻找,我们就会发现:
我们要找的实现类是这个!
点进去!
代码很长,我们直接去到onRefresh() 方法。
点进去:
妈的,继续找实现类:
找到和web相关的实现类进去一看:
来了来了
就是这个方法:
- 获取到容器中已经存在的ServletWebServerFactory对象(是不是有点熟悉?回到第一节结尾处去看看!),然后调用了什么方法?
调用了什么方法????
是不是getWebServer()!!
我们看看ServletWebServerFactory的子类(由于我这里的按理项目有两个Spring版本,不管它,正常情况下面的实现类,一种只有一个):
其中是不是有TomcatServletWebServerFactory?
而这个TomcatServletWebServerFactory是不是在@EnableAutoConfiguration
环节创建好的???
好!!! 现在程序拿到了我们的TomcatServletWebServerFactory,并调用了其中的getWebServer()方法!!!!
好了,最终的决战来了。
getWebServer()方法到底做了什么??
我们到目前为止还没看到TomCat启动的任何蛛丝马迹,getWebServer()方法是我们最后的救命稻草了。
好!开始:
- 创建了一个Tomcat对象,然后一系列赋值。
- 调用了一个叫getTomcatWebServer()的方法。最终返回一个WebServer对象
继续:
new了一个TomcatWebServer对象(WebServer的子类),我们去看看它的构造方法:
一顿非空判断加赋值,最后调用到了initialize()方法。 看到这个名字就知道,这是什么?
初始化
Tomcat启动了。
小总结
在 SpringApplication.run()中,刷新容器的时候,程序会去找到在@EnableAutoConfiguration
阶段创建好的ServletWebServerFactory,它的实现类可能是TomCat可能是Jetty,根据我们项目所引入的依赖自动实例化。
最后由ServletWebServerFactory调用getWebServer()方法启动web容器。
🍅你的点赞收藏是我分享的动力,谢谢。🍅