SpringBoot中的Tomcat是如何启动的?
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
添加如上 Web 的依赖,Spring Boot 就帮我们内置了 Servlet 容器,默认使⽤的是 Tomcat,同样也⽀持修改,⽐如可以使⽤ jetty、Undertow 等。
因为内置了启动容器,应⽤程序可以直接通过 Maven 命令将项⽬编译成可执⾏的 jar 包,通过 java -jar 命令直接启动,不需要再像以前⼀样,打包成 War 包,然后部署在 Tomcat 中。
那么:你知道内置的 Tomcat 在 Spring Boot 中是怎么启动的吗?
从启动⼊⼝分析
如果不知道从哪开始,那么⾄少应该知道 Spring Boot 其实运⾏的就是⼀个 main ⽅法,
本⽂环境:Spring Boot:2.2.2.RELEASE
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
我们点进这个 SpringApplication.run() ⽅法:
public static ConfigurableApplicationContext run(Class<?> primarySource, args) {
return run(new Class[]{primarySource}, args);
}
这列的 run() ⽅法返回的是 ConfigurableApplicationContext 对象,我们继续跟踪这个 run() ⽅法:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
⼜套了⼀层,继续点击这个返回的 run() ⽅法:
public ConfigurableApplicationContext args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
/*
* 1、配置属性
* 设置系统属性 java.awt.headless,为true则启⽤headless模式
* headless模式是应⽤的⼀种配置模式,在服务器缺少显⽰设备、键盘、⿏标等外设的情况下可以使⽤该模式
* ⽐如我们使⽤的Linux服务器就是缺少前述的这些设备,但是⼜需要使⽤这些设备提供的能⼒
*/
configureHeadlessProperty();
/*
* 2、获取,发布应⽤开始启动事件
* 通过SpringFactoriesLoader检索META-INF/spring.factories,
* 到声明的所有SpringApplicationRunListener的实现类并将其实例化,
* 之后逐个调⽤其started()⽅法,⼴播SpringBoot要开始执⾏了
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
/* 发布应⽤开始启动事件 */
listeners.starting();
try {
/* 3、初始化参数 */
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
/*
* 4、配置环境,输出banner
* 创建并配置当前SpringBoot应⽤将要使⽤的Environment(包括配置要使⽤的PropertySource以及Profile),
* 并遍历调⽤所有的SpringApplicationRunListener的environmentPrepared()⽅法,⼴播Environment准备完毕。
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
/* 打印banner,如果在resources⽬录下创建了我们⾃⼰的banner就会进⾏打印,否则默认使⽤spring的 */
Banner printedBanner = printBanner(environment);
/* 5、创建应⽤上下⽂ */
context = createApplicationContext();
/* 通过SpringFactoriesLoader检索META-INF/spring.factories,获取并实例化异常分析器。 */
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
/*
* 6、预处理上下⽂
* 为ApplicationContext加载environment,之后逐个执⾏ApplicationContextInitializer的initialize()⽅法来进⼀步封装ApplicationContext,
* 并调⽤所有的SpringApplicationRunListener的contextPrepared()⽅法,【EventPublishingRunListener只提供了⼀个空的contextPrepared()⽅法】,        * 之后初始化IoC容器,并调⽤SpringApplicationRunListener的contextLoaded()⽅法,⼴播ApplicationContext的IoC加载完成,
* 这⾥就包括通过@EnableAutoConfiguration导⼊的各种⾃动配置类。
*/
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
/* 7、刷新上下⽂ */
refreshContext(context);
/* 8、再⼀次刷新上下⽂,其实是空⽅法,可能是为了后续扩展。 */
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
/
* 9、发布应⽤已经启动的事件 */
listeners.started(context);
/*
* 遍历所有注册的ApplicationRunner和CommandLineRunner,并执⾏其run()⽅法。
* 我们可以实现⾃⼰的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进⾏扩展。
*/
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
/* 10、发布应⽤已经启动完成的监听事件 */
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
如果觉得这个⽅法看的云⾥雾⾥的,那么可以概括为如下⼏步:
1. 配置系统属性
2. 获取监听,发布应⽤开始启动时间
3. 初始化输⼊参数
4. 配置环境,输出banner
5. 创建上下⽂
6. 预处理上下⽂
7. 刷新上下⽂
8. 再次刷新上下⽂
9. 发布应⽤已经启动事件
10. 发布应⽤启动完成事件
⽽我们 Tomcat 的启动主要是在第5步创建上下⽂,以及第 7步刷新上下⽂实现的。
创建上下⽂
第5步中,创建上下⽂主要是调⽤的 createApplicationContext() ⽅法:
protected ConfigurableApplicationContext createApplicationContext() {
/** 1. 根据Web应⽤类型,获取对应的ApplicationContext⼦类 **/
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch(this.webApplicationType) {
case SERVLET:
contextClass = Class.forName("org.springframework.boot.t.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("org.springframework.t.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("t.annotation.AnnotationConfigApplicationContext");
}
servlet和tomcat的关系} catch (ClassNotFoundException var3) {
throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
}
}
/** 2. 实例化⼦类 **/
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
代码中主要就是通过 switch 语句,根据 webApplicationType 的类型来创建不同的 ApplicationContext:
SERVLET:Web类型,实例化 AnnotationConfigServletWebServerApplicationContext
REACTIVE:响应式Web类型,实例化 AnnotationConfigReactiveWebServerApplicationContext
default:⾮Web类型,实例化 AnnotationConfigApplicationContext
因为我们的应⽤是 Web 类型,所以实例化的是 AnnotationConfigServletWebServerApplicationContext,如下是该类的关系图(由Diagram 截图):
我们在上图的底部触发,可以看到 AnnotationConfigServletWebServerApplicationContext > ServletWebServerApplicationContext > ... AbstractApplicationContext(>表⽰继承),总之,最终继承到了 AbstractApplicationContext,这个类是 ApplicationContext 的抽象实现类,该抽象类实现应⽤上下⽂的⼀些具体操作。
⾄此,并没有看到 Tomcat 的相关代码,其实这⼀步主要就是「创建上下⽂」,拿到「上下⽂」之后需要传递给「刷新上下⽂」,交由刷新上下⽂创建 Web 服务。
刷新上下⽂
第7步中,刷新上下⽂时调⽤的 refreshContext(context) ⽅法,其中 context 就是第5步创建的上下⽂,⽅法如下:private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (isterShutdownHook) {
try {
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
refreshContext() ⽅法传递的 context,经由 refresh() ⽅法强转成⽗类 AbstractApplicationContext,具体调⽤过程如下:public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
/** 主要关系 onRefresh() ⽅法 ------------- **/
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
}
}
}
在这个⽅法中我们主要关⼼ onRefresh() ⽅法,onRefresh() ⽅法是调⽤其⼦类实现的,也就是 ServletWebServerApplicationContext,
如下是⼦类的 onRefresh() ⽅法:
protected void onRefresh() {
try {
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = ServletContext();
if (webServer == null && servletContext == null) {
/** 得到Servlet⼯⼚ **/
ServletWebServerFactory factory = WebServerFactory();
this.webServer = WebServer(new ServletContextInitializer[]{SelfInitializer()});
} else if (servletContext != null) {
try {
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
this.initPropertySources();
}
其中 createWebServer() ⽅法是⽤来启动web服务的,但是还没有真正启动 Tomcat,只是通过ServletWebServerFactory 创建了⼀个WebServer,我们继续来看这个 ServletWebServerFactory:
ServletWebServerFactory 有4个实现类,其中我们最常⽤的是 TomcatServletWebServerFactory和JettyServletWebServerFactory,⽽默认的 Web 环境就是 TomcatServletWebServerFactory。
⽽到这总算是看到 Tomcat 相关的字眼了。
来看⼀下 TomcatServletWebServerFactory 的 getWebServer() ⽅法:
public WebServer initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
/** 1、创建Tomcat实例 **/
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : ateTempDir("tomcat");
tomcat.AbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
this.customizeConnector(connector);
/** 2、给创建好的tomcat设置连接器connector **/
tomcat.setConnector(connector);
/** 设置不⾃动部署 **/
/** 3、配置Tomcat容器引擎 **/
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (();
}
/**
* 准备Tomcat的StandardContext,并添加到Tomcat中,同时把initializers 注册到类型为
* TomcatStarter的ServletContainerInitializer中
**/
this.Host(), initializers);
/** 将创建好的Tomcat包装成WebServer返回**/
TomcatWebServer(tomcat);
}
public Engine getEngine() {
Service service = Server().findServices()[0];
if (Container() != null) {
Container();
} else {
Engine engine = new StandardEngine();
engine.setName("Tomcat");
engine.setDefaultHost(this.hostname);
engine.ateDefaultRealm());
service.setContainer(engine);
return engine;
}
}
getWebServer() 这个⽅法创建了 Tomcat 对象,并且做了两件重要的事情:
1. 把连接器 Connector 对象添加到 Tomcat 中;
2. 配置容器引擎,Engine());
⾸先说⼀下这个 Connector 连接器,Tomcat 有两个核⼼功能:
1. 处理 Socket 连接,负责⽹络字节流与 Request 和 Response 对象的转化。
2. 加载和管理 Servlet,以及具体处理 Request 请求。
针对这两个功能,Tomcat 设计了两个核⼼组件来分别完成这两件事,即:连接器(Connector)和容器(Container)。
整个过程⼤致就是:Connector 连接器接收连接请求,创建Request和Response对象⽤于和请求端交换数据,然后分配线程让Engine(也就是Servlet容器)来处理这个请求,并把产⽣的Request和Response对象传给Engine。当Engine处理完请求后,也会通过Connector将响应返回给客户端。
这⾥⾯提到了 Engine,这个是 Tomcat 容器⾥的顶级容器(Container),我们可以通过 Container 类查看其他的⼦容器:Engine、Host、Context、Wrapper
4者的关系是:Engine 是最⾼级别的容器,Engine ⼦容器是 Host,Host 的⼦容器是 Context,Context ⼦容器是 Wrapper,所以这4个容器的关系就是⽗⼦关系,即:Wrapper > Context > Host > Engine (>表⽰继承)
⾄此我们了解了 Engine 这个就是个容器,然后我们再看⼀下这个 Engine()) 具体⼲了啥:private void configureEngine(Engine engine) {
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
Iterator var2 = ineValves.iterator();
while(var2.hasNext()) {
Valve valve = (();
}
}
其中 engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay) 是指定背景线程的执⾏间隔,例如背景线程会在每隔多长时间后判断session是否失效之类。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。