作者:李岩科

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();

//Tomcat.java

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启动过程及原理的更多相关文章

  1. SpringBoot内置tomcat启动原理

    前言          不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springb ...

  2. springboot学习笔记:6.内置tomcat启动和外部tomcat部署总结

    springboot的web项目的启动主要分为: 一.使用内置tomcat启动 启动方式: 1.IDEA中main函数启动 2.mvn springboot-run 命令 3.java -jar XX ...

  3. Spring Boot 添加jersey-mvc-freemarker依赖后内置tomcat启动不了解决方案

    我在我的Spring Boot 项目的pom.xml中添加了jersey-mvc-freemarker依赖后,内置tomcat启动不了. 报错信息如下: org.springframework.con ...

  4. Spring Boot:内置tomcat启动和外部tomcat部署总结

    springboot的web项目的启动主要分为: 一.使用内置tomcat启动 启动方式: 1.IDEA中main函数启动 2.mvn springboot-run 命令 3.java -jar XX ...

  5. springboot内置tomcat验证授权回调页面域名

    springboot内置tomcat验证公众号授权回调页面域名 解决方法: 网上下载一个tomcat,在server.xml文件中修改端口为springboot内置tomcat的端口号,复制验证文件到 ...

  6. 去除springboot内置tomcat

    /** * @author zx * @title: ServletInitializer * @projectName activiti * @description: 解决内置tomcat * @ ...

  7. springboot不使用内置tomcat启动,用jetty或undertow

    Spring Boot启动程序通常使用Tomcat作为默认的嵌入式服务器.如果需要更改 - 您可以排除Tomcat依赖项并改为包含Jetty或Undertow: jetty配置: <depend ...

  8. 1.springboot内置tomcat的connection相关

    最近在研究tomcat的连接超时问题,环境:jdk1.8 + springboot 2.1.1.RELEASE,以下仅为个人理解,如果异议,欢迎指正. springboot的tomcat的几个配置参数 ...

  9. Springboot 内置tomcat 基本配置收集整理

    配置一: server:# tomcat 配置  tomcat:    # 接收队列长度    accept-count: 1000    # 最小空闲线程数    min-spare-threads ...

  10. eclipse内置tomcat启动方法

    tomcat:run -Dmaven.tomcat.port=

随机推荐

  1. 详解centos7中配置keepalived日志为别的路径

    由于在默认状态下keepalived的日志会写入到/var/log/message中,我们需要将此剥离出来. 在centos 6下可以: (1)首先修改/etc/sysconfig/keepalive ...

  2. Springboot配置文件参数使用docker-compose实现动态配置

    文章总结; Springboot配置文件中的一些参数可以写成变量的形式,具体变量的值可以从docker-compose.yml文件中设置来获取 在yml文件中,通过${Envirment_variab ...

  3. 面试突击86:SpringBoot 事务不回滚?怎么解决?

    在 Spring Boot 中,造成事务不自动回滚的场景有很多,比如以下这些: 非 public 修饰的方法中的事务不自动回滚: 当 @Transactional 遇上 try/catch 事务不自动 ...

  4. Java之POI导出Excel(二):多个sheet

    相信在大部分的web项目中都会有导出导入Excel的需求,之前我也写过一篇导出单个sheet工作表的文章,没看过的小伙伴可以去看哈,链接也给大家放出来了:导出单个sheet 但是在我们日常的工作中,需 ...

  5. 手把手教你玩转 Gitea|使用 Helm 在 K3s 上安装 Gitea

    前言 在前面的文章中,演示了如何用 Docker 镜像和 Windows 二进制包来安装运行 Gitea.今天是玩转 Gitea 系列的使用 Helm 在 K3s 上安装 Gitea. 关于 Gite ...

  6. Pep9课下作业

    (1)编写一个伪代码算法 Set sum to 0 Input num1 Read num1 Set sum to sum + num1 Input num2 Read num2 Set sum to ...

  7. Springboot+vue 实现汽车租赁系统(毕业设计二)(前后端项目分离)

    文章目录 1.系统功能列表 2.管理员端界面 2.1 商家登录界面 2.2 用户信息管理界面 2.3 汽车管理界面 2.4 订单界面 2.5 汽车图形报表 2.6 优惠券新增界面 3.普通用户界面 3 ...

  8. 2、yaml配置文件当中的坑(数字的定义和支持进制书写格式)

    6.进制数的转换 6.1.问题回顾 我记得我刚刚开始使用SpringBoot的时候,有一天在做到SpringBoot整合第三方技术的时候 我刚好在那天学习到整合Mybatis,做Web项目嘛,不连数据 ...

  9. cmd中pip加速的方法

    临时加速: pip install dlib -i https://pypi.tuna.tsinghua.edu.cn/simple/ 永久加速: 在user文件夹里新建pip文件夹,再建pip.in ...

  10. 恭喜磊哥喜提n+1

    昨天下午两点多磊哥突然喊我下楼,第一反应是"这孙子,抽烟就直说,还说个事,你以外你是吉祥村大姐啊". 心里骂完以后我慢慢悠悠下楼了,见他在打电话我先默默点上一支,准备待他结束以后对 ...