SpringBoot内置tomcat启动过程及原理
作者:李岩科
1 背景
SpringBoot 是一个框架,一种全新的编程规范,他的产生简化了框架的使用,同时也提供了很多便捷的功能,比如内置 tomcat 就是其中一项,他让我们省去了搭建 tomcat 容器,生成 war,部署,启动 tomcat。因为内置了启动容器,应用程序可以直接通过 Maven 命令将项目编译成可执行的 jar 包,通过 java -jar 命令直接启动,不需要再像以前一样,打包成 War 包,然后部署在 Tomcat 中。那么内置 tomcat 是如何实现的呢
2 tomcat 启动过程及原理
2.1 下载一个 springboot 项目
在这里下载一个项目 https://start.spring.io/ 也可以在 idea 新建 SpringBoot-Web 工程.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
点击 pom.xml 会有 tomcat 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
2.2 从启动入口开始一步步探索
点击进入 run 方法
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
//继续点击进入run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
进入到这个 run 方法之后就可以看到,我们认识的一些初始化事件。主要的过程也是在这里完成的。
2.3 源码代码流程大致是这样
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
/**1、配置系统属性*/
configureHeadlessProperty();
/**2.获取监听器*/
SpringApplicationRunListeners listeners = getRunListeners(args);
/**发布应用开始启动事件 */
listeners.starting();
try {
/** 3.初始化参数 */
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
/** 4.配置环境*/
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
/**5.创建应用上下文*/
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
/**6.预处理上下文*/
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
/**6.刷新上下文*/
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
/** 8.发布应用已经启动事件 */
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
/** 9.发布应用已经启动完成的监听事件 */
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
代码中主要就是通过 switch 语句,根据 webApplicationType 的类型来创建不同的 ApplicationContext:
- DEFAULT_SERVLET_WEB_CONTEXT_CLASS:Web 类型,实例化 AnnotationConfigServletWebServerApplicationContext
- DEFAULT_REACTIVE_WEB_CONTEXT_CLASS:响应式 Web 类型,实例化 AnnotationConfigReactiveWebServerApplicationContext
- DEFAULT_CONTEXT_CLASS:非 Web 类型,实例化 AnnotationConfigApplicationContext
2.4 创建完应用上下文之后,我们在看刷新上下文方法
一步步通过断点点击方法进去查看,我们看到很熟悉代码 spring 的相关代码。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//初始化前的准备工作,主要是一些系统属性、环境变量的校验,比如Spring启动需要某些环境变量,可以在这个地方进行设置和校验
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//准备bean工厂 注册了部分类
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//注册bean工厂后置处理器,并解析java代码配置bean定义
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//注册bean后置处理器,并不会执行后置处理器,在后面实例化的时候执行
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
//初始化事件监听多路广播器
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//待子类实现,springBoot在这里实现创建内置的tomact容器
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
2.5 onRefresh () 方法是调用其子类实现的
也就是 ServletWebServerApplicationContext
/** 得到Servlet工厂 **/
this.webServer = factory.getWebServer(getSelfInitializer());
其中 createWebServer () 方法是用来启动 web 服务的,但是还没有真正启动 Tomcat,只是通过 ServletWebServerFactory 创建了一个 WebServer,继续来看这个 ServletWebServerFactory:
this.webServer = factory.getWebServer (getSelfInitializer ()); 这个方法可以看出 TomcatServletWebServerFactory 的实现。相关 Tomcat 的实现。
2.6 TomcatServletWebServerFactory 的 getWebServer () 方法
清晰的看到 new 出来了一个 Tomcat.
2.7 Tomcat 创建之后,继续分析 Tomcat 的相关设置和参数
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
/** 1、创建Tomcat实例 **/
Tomcat tomcat = new Tomcat();
//创建Tomcat工作目录
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
/** 2、给创建好的tomcat设置连接器connector **/
tomcat.setConnector(connector);
/** 3.设置不自动部署 **/
tomcat.getHost().setAutoDeploy(false);
/** 4.配置Tomcat容器引擎 **/
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
/**准备Tomcat的StandardContext,并添加到Tomcat中*/
prepareContext(tomcat.getHost(), initializers);
/** 将创建好的Tomcat包装成WebServer返回**/
return getTomcatWebServer(tomcat);
}
2.8 继续点击 getTomcatWebServer 方法,找到 initialize () 方法,可以看到 tomcat.start (); 启动 tomcat 服务方法。
// Start the server to trigger initialization listeners
//启动tomcat服务
this.tomcat.start();
//开启阻塞非守护进程
startDaemonAwaitThread();
2.9 TomcatWebServer.java 控制台会打印这句话
Tomcat started on port(s): 8080 (http) with context path ‘’
3 总结
SpringBoot 的启动主要是通过实例化 SpringApplication 来启动的,启动过程主要做了如下几件事情:
配置系统属性、获取监听器,发布应用开始启动事件、初始化参数、配置环境、创建应用上下文、预处理上下文、刷新上下文、再次刷新上下文、发布应用已经启动事件、发布应用启动完成事件。而启动 Tomcat 是刷新上下文 这一步。
Spring Boot 创建 Tomcat 时,会先创建一个上下文,将 WebApplicationContext 传给 Tomcat;
启动 Web 容器,需要调用 getWebserver (),因为默认的 Web 环境就是 TomcatServletWebServerFactory,所以会创建 Tomcat 的 Webserver,这里会把根上下文作为参数给 TomcatServletWebServerFactory 的 getWebServer ();启动 Tomcat,调用 Tomcat 中 Host、Engine 的启动方法。
3.1 Tomcat 相关名称介绍
SpringBoot内置tomcat启动过程及原理的更多相关文章
- SpringBoot内置tomcat启动原理
前言 不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springb ...
- springboot学习笔记:6.内置tomcat启动和外部tomcat部署总结
springboot的web项目的启动主要分为: 一.使用内置tomcat启动 启动方式: 1.IDEA中main函数启动 2.mvn springboot-run 命令 3.java -jar XX ...
- Spring Boot 添加jersey-mvc-freemarker依赖后内置tomcat启动不了解决方案
我在我的Spring Boot 项目的pom.xml中添加了jersey-mvc-freemarker依赖后,内置tomcat启动不了. 报错信息如下: org.springframework.con ...
- Spring Boot:内置tomcat启动和外部tomcat部署总结
springboot的web项目的启动主要分为: 一.使用内置tomcat启动 启动方式: 1.IDEA中main函数启动 2.mvn springboot-run 命令 3.java -jar XX ...
- springboot内置tomcat验证授权回调页面域名
springboot内置tomcat验证公众号授权回调页面域名 解决方法: 网上下载一个tomcat,在server.xml文件中修改端口为springboot内置tomcat的端口号,复制验证文件到 ...
- 去除springboot内置tomcat
/** * @author zx * @title: ServletInitializer * @projectName activiti * @description: 解决内置tomcat * @ ...
- springboot不使用内置tomcat启动,用jetty或undertow
Spring Boot启动程序通常使用Tomcat作为默认的嵌入式服务器.如果需要更改 - 您可以排除Tomcat依赖项并改为包含Jetty或Undertow: jetty配置: <depend ...
- 1.springboot内置tomcat的connection相关
最近在研究tomcat的连接超时问题,环境:jdk1.8 + springboot 2.1.1.RELEASE,以下仅为个人理解,如果异议,欢迎指正. springboot的tomcat的几个配置参数 ...
- Springboot 内置tomcat 基本配置收集整理
配置一: server:# tomcat 配置 tomcat: # 接收队列长度 accept-count: 1000 # 最小空闲线程数 min-spare-threads ...
- eclipse内置tomcat启动方法
tomcat:run -Dmaven.tomcat.port=
随机推荐
- Nginx支持web界面执行bash|python等系统命令和脚本,可以传递参数
文章转载自:https://me.jinchuang.org/archives/114.html ,有修改 步骤总结 1.安装好nginx,假设其html根路径为/usr/share/nginx/ht ...
- 第二章:视图层 - 9:动态生成CSV文件
CSV (Comma Separated Values),以纯文本形式存储数字和文本数据的存储方式.纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样的数据.CSV文件由任意数目的记录组成,记 ...
- k8s中安装redis6集群
注意:第二步不用操作了,yaml文件仅供参考 1.创建Namespace kubectl apply -f Namespace.yaml apiVersion: v1 kind: Namespace ...
- 为不同的用户生成不同的 Kibana 界面
文件转载自:https://elasticstack.blog.csdn.net/article/details/109593613
- 1.在 Kubernetes 在快速安装 Harbor
网址:https://www.qikqiak.com/post/harbor-quick-install/ 安装 Harbor Harbor 支持多种安装方式,源码目录下面默认有一个安装脚本(make ...
- 洛谷P4408 [NOI2003] 逃学的小孩 (树的直径)
本题就是从c到a/b再到b/a距离的最大值,显然,a和b分别是树的直径的两个端点,先用两次dfs求出树的直径,再用一次dfs求出每个点到a的距离,最后再用一次dfs求出每个点到距离它较近的a/b的距离 ...
- JUC(5)BlockingQueue四组API
1.读写锁ReadWriteLock package com.readlock; import java.util.HashMap; import java.util.Map; /** * ReadW ...
- k8s 中的 ingress 使用细节
k8s中的ingress 什么是ingress Ingress 如何使用 ingress 使用细节 参考 k8s中的ingress 什么是ingress k8s 中使用 Service 为相同业务的 ...
- win10 优化
禁用服务中的某些不用的服务 例如 SysMain 服务 win+R键开启运行快捷方式,输入services.msc: 找到SysMain这个服务: 选择这个服务后,右键属性: 点击停止:启动类型选择禁 ...
- 一个 MySQL 隐式转换的坑,差点把服务器整崩溃了
我是风筝,公众号「古时的风筝」,专注于 Java技术 及周边生态. 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面. 本来是一个平静而美好的下午,其 ...