作者:李岩科

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 工程.

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-web</artifactId>
  4. </dependency>

点击 pom.xml 会有 tomcat 依赖

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-tomcat</artifactId>
  4. <version>2.1.2.RELEASE</version>
  5. <scope>compile</scope>
  6. </dependency>

2.2 从启动入口开始一步步探索

点击进入 run 方法

  1. public static ConfigurableApplicationContext run(Class<?> primarySource,
  2. String... args) {
  3. return run(new Class<?>[] { primarySource }, args);
  4. }
  5. //继续点击进入run方法
  6. public static ConfigurableApplicationContext run(Class<?>[] primarySources,
  7. String[] args) {
  8. return new SpringApplication(primarySources).run(args);
  9. }

进入到这个 run 方法之后就可以看到,我们认识的一些初始化事件。主要的过程也是在这里完成的。

2.3 源码代码流程大致是这样

  1. /**
  2. * Run the Spring application, creating and refreshing a new
  3. * {@link ApplicationContext}.
  4. * @param args the application arguments (usually passed from a Java main method)
  5. * @return a running {@link ApplicationContext}
  6. */
  7. public ConfigurableApplicationContext run(String... args) {
  8. StopWatch stopWatch = new StopWatch();
  9. stopWatch.start();
  10. ConfigurableApplicationContext context = null;
  11. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  12. /**1、配置系统属性*/
  13. configureHeadlessProperty();
  14. /**2.获取监听器*/
  15. SpringApplicationRunListeners listeners = getRunListeners(args);
  16. /**发布应用开始启动事件 */
  17. listeners.starting();
  18. try {
  19. /** 3.初始化参数 */
  20. ApplicationArguments applicationArguments = new DefaultApplicationArguments(
  21. args);
  22. /** 4.配置环境*/
  23. ConfigurableEnvironment environment = prepareEnvironment(listeners,
  24. applicationArguments);
  25. configureIgnoreBeanInfo(environment);
  26. Banner printedBanner = printBanner(environment);
  27. /**5.创建应用上下文*/
  28. context = createApplicationContext();
  29. exceptionReporters = getSpringFactoriesInstances(
  30. SpringBootExceptionReporter.class,
  31. new Class[] { ConfigurableApplicationContext.class }, context);
  32. /**6.预处理上下文*/
  33. prepareContext(context, environment, listeners, applicationArguments,
  34. printedBanner);
  35. /**6.刷新上下文*/
  36. refreshContext(context);
  37. afterRefresh(context, applicationArguments);
  38. stopWatch.stop();
  39. if (this.logStartupInfo) {
  40. new StartupInfoLogger(this.mainApplicationClass)
  41. .logStarted(getApplicationLog(), stopWatch);
  42. }
  43. /** 8.发布应用已经启动事件 */
  44. listeners.started(context);
  45. callRunners(context, applicationArguments);
  46. }
  47. catch (Throwable ex) {
  48. handleRunFailure(context, ex, exceptionReporters, listeners);
  49. throw new IllegalStateException(ex);
  50. }
  51. try {
  52. /** 9.发布应用已经启动完成的监听事件 */
  53. listeners.running(context);
  54. }
  55. catch (Throwable ex) {
  56. handleRunFailure(context, ex, exceptionReporters, null);
  57. throw new IllegalStateException(ex);
  58. }
  59. return context;
  60. }

代码中主要就是通过 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 的相关代码。

  1. @Override
  2. public void refresh() throws BeansException, IllegalStateException {
  3. synchronized (this.startupShutdownMonitor) {
  4. // Prepare this context for refreshing.
  5. //初始化前的准备工作,主要是一些系统属性、环境变量的校验,比如Spring启动需要某些环境变量,可以在这个地方进行设置和校验
  6. prepareRefresh();
  7. // Tell the subclass to refresh the internal bean factory.
  8. ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
  9. // Prepare the bean factory for use in this context.
  10. //准备bean工厂 注册了部分类
  11. prepareBeanFactory(beanFactory);
  12. try {
  13. // Allows post-processing of the bean factory in context subclasses.
  14. postProcessBeanFactory(beanFactory);
  15. // Invoke factory processors registered as beans in the context.
  16. //注册bean工厂后置处理器,并解析java代码配置bean定义
  17. invokeBeanFactoryPostProcessors(beanFactory);
  18. // Register bean processors that intercept bean creation.
  19. //注册bean后置处理器,并不会执行后置处理器,在后面实例化的时候执行
  20. registerBeanPostProcessors(beanFactory);
  21. // Initialize message source for this context.
  22. initMessageSource();
  23. // Initialize event multicaster for this context.
  24. //初始化事件监听多路广播器
  25. initApplicationEventMulticaster();
  26. // Initialize other special beans in specific context subclasses.
  27. //待子类实现,springBoot在这里实现创建内置的tomact容器
  28. onRefresh();
  29. // Check for listener beans and register them.
  30. registerListeners();
  31. // Instantiate all remaining (non-lazy-init) singletons.
  32. finishBeanFactoryInitialization(beanFactory);
  33. // Last step: publish corresponding event.
  34. finishRefresh();
  35. }
  36. catch (BeansException ex) {
  37. if (logger.isWarnEnabled()) {
  38. logger.warn("Exception encountered during context initialization - " +
  39. "cancelling refresh attempt: " + ex);
  40. }
  41. // Destroy already created singletons to avoid dangling resources.
  42. destroyBeans();
  43. // Reset 'active' flag.
  44. cancelRefresh(ex);
  45. // Propagate exception to caller.
  46. throw ex;
  47. }
  48. finally {
  49. // Reset common introspection caches in Spring's core, since we
  50. // might not ever need metadata for singleton beans anymore...
  51. resetCommonCaches();
  52. }
  53. }
  54. }

2.5 onRefresh () 方法是调用其子类实现的

也就是 ServletWebServerApplicationContext

  1. /** 得到Servlet工厂 **/
  2. 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 的相关设置和参数

  1. @Override
  2. public WebServer getWebServer(ServletContextInitializer... initializers) {
  3. /** 1、创建Tomcat实例 **/
  4. Tomcat tomcat = new Tomcat();
  5. //创建Tomcat工作目录
  6. File baseDir = (this.baseDirectory != null) ? this.baseDirectory
  7. : createTempDir("tomcat");
  8. tomcat.setBaseDir(baseDir.getAbsolutePath());
  9. Connector connector = new Connector(this.protocol);
  10. tomcat.getService().addConnector(connector);
  11. customizeConnector(connector);
  12. /** 2、给创建好的tomcat设置连接器connector **/
  13. tomcat.setConnector(connector);
  14. /** 3.设置不自动部署 **/
  15. tomcat.getHost().setAutoDeploy(false);
  16. /** 4.配置Tomcat容器引擎 **/
  17. configureEngine(tomcat.getEngine());
  18. for (Connector additionalConnector : this.additionalTomcatConnectors) {
  19. tomcat.getService().addConnector(additionalConnector);
  20. }
  21. /**准备Tomcat的StandardContext,并添加到Tomcat中*/
  22. prepareContext(tomcat.getHost(), initializers);
  23. /** 将创建好的Tomcat包装成WebServer返回**/
  24. return getTomcatWebServer(tomcat);
  25. }

2.8 继续点击 getTomcatWebServer 方法,找到 initialize () 方法,可以看到 tomcat.start (); 启动 tomcat 服务方法。

  1. // Start the server to trigger initialization listeners
  2. //启动tomcat服务
  3. this.tomcat.start();
  4. //开启阻塞非守护进程
  5. 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. 第五章:Admin管理后台 - 1:自定制Admin

    如果只是在admin中简单的展示及管理模型,那么在admin.py模块中使用admin.site.register将模型注册一下就好了: from django.contrib import admi ...

  2. 第六章:Django 综合篇 - 3:使用MySQL数据库

    在实际生产环境,Django是不可能使用SQLite这种轻量级的基于文件的数据库作为生产数据库.一般较多的会选择MySQL. 下面介绍一下如何在Django中使用MySQL数据库. 一.安装MySQL ...

  3. 使用 Elastic Stack 分析地理空间数据 (一)

    文章转载自:https://blog.csdn.net/UbuntuTouch/article/details/106531939 随着人类在不断地探索空间,地理空间数据越来越多. 收集信息的速度以及 ...

  4. Loki 简明教程

    文章转载参考自:https://jishuin.proginn.com/p/763bfbd2ac34 Loki 是 Grafana Labs 团队最新的开源项目,是一个水平可扩展,高可用性,多租户的日 ...

  5. LcdTools如何导出内置画面为bmp图片

    运行LcdTools,先设置好图片所需分辨率参数,点击"画面设置"栏,修改下图所示参数 点击"画面设置"栏,在"画面资源"栏找到需要导出的画 ...

  6. 变量的复制&传递

    变量的复制 变量的类型 可以分为基本数据类型(Null.Undefined.Number.String.Boolean)和引用类型(Funtion.Object.Array) 基本数据类型是按照值访问 ...

  7. Vue ref 和 v-for 结合(ref 源码解析)

    前言 Vue 中组件的使用很方便,而且直接取组件实例的属性方法等也很方便,其中通过 ref 是最普遍的. 平时使用中主要是对一个组件进行单独设置 ref ,但是有些场景下可能是通过给定数据渲染的,这时 ...

  8. Linux下MMDetection环境配置

    1. 准备工作 Linux发行版. Pop!_OS 22.04 LTS (NVIDIA) (Ubuntu衍生) 对Linux进行配置,更改国内镜像源. 安装conda环境. 官网下载安装脚本(bash ...

  9. Java多线程-ThreadPool线程池(三)

    开完一趟车完整的过程是启动.行驶和停车,但老司机都知道,真正费油的不是行驶,而是长时间的怠速.频繁地踩刹车等动作.因为在速度切换的过程中,发送机要多做一些工作,当然就要多费一些油. 而一个Java线程 ...

  10. React 函数组件

    React 函数组件 1.定义方式 React 函数组件是指使用函数方法定义的组件. 定义方式:与函数的定义方式相同,需要将内容 return 出来,需要注意的是最外层只有一个标签或者使用<&g ...