三分钟了解 SpringBoot 的启动流程

news2024/10/6 17:25:25

一、前言

背景:最近有位开发同学说面试被问到Spring Boot 的启动流程,以及被问到Spring Boot 的嵌入式Web容器是什么时候加载的。如何加载的。是怎么无缝切换的。

这些问题,其实回答起来也是比较复杂的。我们今天就从 SpringApplication.run(EasyPaasAdminApplication.class, args);入口,逐渐向下看下执行流程。来试着回答一下前面这两个问题。

后面关于SpringBoot 的web容器可以无缝随意切换为jetty,undertow…这个问题的回答涉及到Spring Boot是如何设计WebServer的。我们后续专门讲解一下。

二、执行逻辑梳理

一般我们SpringBoot 应用的启动入口都是如下这种固定的写法,
在这里插入图片描述
也可以是这样

 public static void main(String[] args) {
 	SpringApplication application = new SpringApplication(MyApplication.class);
   	// ... customize application settings here
  	application.run(args)
  }

跟过来就到这,可以看到注释运行Spring应用程序,创建并刷新一个新的ApplicationContext。
在这里插入图片描述
在这里插入图片描述
跟代码到这儿其实我们对于SpringBoot 的基本启动流程已经知道了。但是要解答什么时候启动的Tomcat 还需要继续分析。
在这里插入图片描述
到这儿我们就可以继续下去,发现Spring Boot 启动WebServer。此处的WebServer我就不展开了,可以点击去就三个方法start ,stop,getPort。可以看出来Spring 在设计接口的时候还是很严谨和精简。

我们的核心脉络是梳理SpringBoot 启动过程,并且回答Tomcat 是如何被启动的。
在这里插入图片描述
我们可以看到WebServer 的实现目前内置的有5种。其实Spring Boot 还有一个特性叫做 自动装配。

这就是为什么5个实现,我们最后启动的是Tomcat。此处也不做展开。后面我专门搞一个解析SpringBoot 自动装配的文章。
在这里插入图片描述
我们看一下内部start 的TomcatWebServer的内部实现。了解过Tomcat 源码的同学看到这儿就基本明白了。
在这里插入图片描述
好源码跟进过程我们到此结束,我们整理和总结一下。

通过扫一遍源码我们大概可以总结出来如下三个阶段:

  • 准备阶段 :Spring Boot 会加载应用程序的初始设置,并创建 Spring Boot 上下文。这个阶段的核心源码是SpringApplication 类的 run() 方法,它会调用 Spring Boot 的各个初始化器进行初始化和准备工作。
  • 应用上下文创建阶段 : Spring Boot 会创建应用程序的上下文,包括各种配置信息、Bean 的加载和初始化等。这个阶段的核心源码是Spring Boot 自动配置机制,通过扫描 classpath 中的配置文件,自动加载和配置各种组件和 Bean。
  • 刷新上下文阶段 :Spring Boot 会执行各种启动任务,包括创建 Web服务器、加载应用程序的配置、初始化各种组件等。这个阶段的核心源码是 Spring Boot的刷新机制,它会调用各种初始化器和监听器,执行各种启动任务。其中启动Tomcat 就是在这个环节进行。

三、核心源码解析

1、准备阶段

在准备阶段中,Spring Boot 会加载应用程序的初始设置,并创建 Spring Boot 上下文。这个阶段的核心源码是 SpringApplication 类的 run() 方法,它会调用 Spring Boot 的各个初始化器进行初始化和准备工作。

public ConfigurableApplicationContext run(String... args) {
        // 启动计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        // 定义应用程序上下文和异常报告器列表
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

        // 配置 Headless 属性
        configureHeadlessProperty();

        // 获取 Spring Boot 启动监听器
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 执行启动监听器的 starting 方法
        listeners.starting();

        try {
            // 解析命令行参数
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备应用程序环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            // 配置忽略 BeanInfo
            configureIgnoreBeanInfo(environment);
            // 打印 Banner
            Banner printedBanner = printBanner(environment);
            // 创建应用程序上下文
            context = createApplicationContext();
            // 获取异常报告器,关于异常报告,我下次专门讲一下SpringBoot 的异常收集器。
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            // 准备应用程序上下文
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新应用程序上下文
            refreshContext(context);
            // 刷新后操作
            afterRefresh(context, applicationArguments);
            // 停止计时器
            stopWatch.stop();
            // 记录启动日志
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 执行启动监听器的 started 方法
            listeners.started(context);
            // 执行 Runner
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 处理启动失败
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // 执行启动监听器的 running 方法
            listeners.running(context);
        } catch (Throwable ex) {
            // 处理启动失败
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }

        // 返回应用程序上下文
        return context;
    }

在 run() 方法中,Spring Boot 首先会创建一个 StopWatch 对象,用于记录整个启动过程的耗时。然后,Spring Boot 会调用 getRunListeners(args) 方法获取 Spring Boot 的各个启动监听器,并调用starting() 方法通知这些监听器启动过程已经开始。接着调用 prepareEnvironment(listeners, applicationArguments) 方法创建应用程序的环境变量。

这个方法会根据用户的配置和默认设置创建一个 ConfigurableEnvironment对象,并将其传给后面的 createApplicationContext() 方法。printBanner(environment) 方法打印启动界面的 Banner,调用 refreshContext(context)方法刷新上下文。这个方法会启动上下文,执行各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。具体的启动任务会在刷新上下文阶段中进行。

2、应用上下文创建阶段

在应用上下文创建阶段中,Spring Boot 会创建应用程序的上下文,包括各种配置信息、Bean 的加载和初始化等。这个阶段的核心源码是 Spring Boot 自动配置机制,通过扫描 classpath 中的配置文件,自动加载和配置各种组件和 Bean。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            switch (this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
            }
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable to create a default ApplicationContext, " +
                    "please specify an ApplicationContextClass", ex);
        }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

在 createApplicationContext() 方法中,Spring Boot 首先会判断应用程序的类型,如果是 Web 应用程序,则会创建一个 WebApplicationContext;否则,会创建一个普通的 ApplicationContext。调用 BeanUtils.instantiateClass(contextClass) 方法创建应用程序的上下文。这个方法会根据上面的逻辑创建一个相应的 ApplicationContext。调用 load() 方法加载应用程序的配置。

这个方法会扫描 classpath 中的各种配置文件,例如 application.properties、application.yml、META-INF/spring.factories 等,自动配置各种组件和 Bean。调用 postProcessApplicationContext() 方法对应用程序的上下文进行后处理。这个方法会调用各种初始化器和监听器,执行各种初始化任务。

3、刷新上下文阶段

在刷新上下文阶段中,Spring Boot 会执行各种启动任务,包括创建 Web 服务器(刚才我们跟源码的时候也看到了,如上我的截图)、加载应用程序的配置、初始化各种组件等。这个阶段的核心源码是 Spring Boot 的刷新机制,它会调用各种初始化器和监听器,执行各种启动任务。

protected void refreshContext(ConfigurableApplicationContext applicationContext) {
    refresh(applicationContext);
    if (this.registerShutdownHook) {
        try {
            applicationContext.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

在 refreshContext() 方法中调用 refresh(applicationContext) 方法刷新上下文。这个方法是 ApplicationContext 接口的核心方法,会启动上下文,执行各种启动任务。调用 registerShutdownHook() 方法注册应用程序的关闭钩子。这个方法会在应用程序关闭时自动执行,清理资源、关闭线程等,所以我们利用此特性在服务关闭的时候清理一些资源。并向外部发送告警通知。

在 refresh(applicationContext) 方法中,Spring Boot 会执行上下文的各种启动任务,包括创建 Web 服务器、加载应用程序的配置、初始化各种组件等。具体的启动任务会调用各种初始化器和监听器,例如:

for (ApplicationContextInitializer<?> initializer : getInitializers()) {
    initializer.initialize(applicationContext);
}

另外,Spring Boot 还会调用各种监听器,我们不做赘述,例如:

for (ApplicationListener<?> listener : getApplicationListeners()) {
    if (listener instanceof SmartApplicationListener) {
        SmartApplicationListener smartListener = (SmartApplicationListener) listener;
        if (smartListener.supportsEventType(eventType)
                && smartListener.supportsSourceType(sourceType)) {
            invokeListener(smartListener, event);
        }
    }
    else if (supportsEvent(listener, eventType)) {
        invokeListener(listener, event);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/742846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D设计建模软件The Foundry Modo 16对Mac和Windows的系统要求

Foundry MODO是一款功能强大的三维建模、动画和渲染软件。它为艺术家和设计师提供了一套全面的工具&#xff0c;可以用来创建令人惊叹的视觉效果。无论是制作电影、电视节目、游戏还是其他数字媒体内容&#xff0c;MODO都可以满足您的需求。 MODO具有直观的用户界面&#xff0…

redis主从配置

从redis配置&#xff1a;redis6386.conf include /data/redis/redis6380.conf #主redis配置文件路径&#xff08;这里是引用主配置文件里的配置在修改从配置&#xff09; daemonize yes #在后台启动 protected-mode no #加密保护关闭 bind 192.168.3.*** requirepas…

云主机安全-私有密钥安全认证

场景描述 云主机凭借其性价比高、生配扩容便利、运维便捷、稳定性高等优势深受用户青睐&#xff0c;越来越多的企业开始租用云主机&#xff0c;将自己的服务器、业务系统等搭建或存储到云主机上。 用户痛点 用户租用或托管的云主机&#xff0c;运维端口&#xff08;远程桌面&…

SSM框架最新整合保姆级教程(IDEA版)

SSM框架最新整合保姆级教程(IDEA版) 一、环境要求 ​ 环境&#xff1a; IDEAMySQL 5.7.19Tomcat 9Maven 3.6 要求&#xff1a; 需要熟练掌握MySQL数据库&#xff0c;Spring&#xff0c;JavaWeb及MyBatis知识&#xff0c;简单的前端知识&#xff1b; 完整代码&#xff1a;…

Openlayers实战:绘制带箭头的线

Openlayers地图中有的时候会用到这样的场景,连续画几段线段,但是要知道绘制的方向,给人以指引的提示作用。 怎么绘制呢? 在本实战中,主要的是处理线段的显示方式,在线段的拐点处附加上箭头图片,具体看实际的源代码。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhu…

4 生成全排列

4 生成全排列 作者: 赵晓鹏时间限制: 1S章节: 递归与分治 输入范例 : 无 输出范例 : Perm1(1):123 Perm1(2):123 Perm1(2):213 Perm1(2):321 Perm1(3):123 Perm1(3):132 Perm1(3):213 Perm1(3):231 Perm1(3):321 Perm1(3):312 Online Judge 1.0 #include<iostream> …

【全栈开发指南】数据权限使用配置

数据权限配置有两种方式&#xff1a; 通过系统配置界面&#xff0c;实时配置生效。 通过代码注解配置。 一、通过系统配置界面配置数据权限 系统配置的数据权限是通过系统配置界面将配置信息保存在数据库&#xff0c;然后系统启动时&#xff0c;将配置信息保存到Redis缓存来…

SpringCloud分布式搜索引擎、数据聚合、ES和MQ的结合使用、ES集群的问题

目录 数据聚合 聚合的分类 ​编辑 DSL实现Bucket聚合 ​编辑 DSL实现Metrics聚合​编辑 RestAPI实现聚合 对接前端接口​编辑 自定义分词器​编辑 Completion suggester查询 Completion suggester查询 酒店数据自动补全 实现酒店搜索框界面输入框的自动补全 数据同步问…

Codeforces Round 875 (Div. 1) A. Copil Copac Draws Trees

题意 Copil Copac 给定了一个由 n−1 条边组成的列表&#xff0c;该列表描述了一棵由 n 个顶点组成的树。他决定用下面的算法来绘制它&#xff1a; 步骤 0&#xff1a;绘制第一个顶点&#xff08;顶点1&#xff09;。转到步骤1。 步骤 1&#xff1a;对于输入中的每一条边&#…

Window10 系统 RabbitMQ的安装和简单使用

1、下载 & 安装 Erlang 因为RabbitMQ的服务端是基于 Erlang编写的&#xff0c;所以&#xff0c;首先需要安装Erlang。 1&#xff09;下载 下载地址如下&#xff1a; https://www.erlang.org/downloads此处下载比较慢&#xff0c;可以参考如下百度网盘&#xff1a; 链接…

常用的缓存工具有ehcache、memcache和redis,这里介绍spring中ehcache的配置。

常用的缓存工具有ehcache、memcache和redis&#xff0c;这里介绍spring中ehcache的配置。 1.在pom添加依赖&#xff1a; <!-- ehcache 相关依赖 --><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache</artifactId><ve…

正则表达式测试(二)

一、中括号的语法 匹配所有的字符&#xff0c;返回一个数组,包含匹配的所有字符内容&#xff0c;按顺序展开&#xff1b; 注意&#xff1a;空格也会被匹配到 匹配所有符合的字符&#xff0c;返回一个数组。 匹配空白字符 匹配非空白字符 匹配 空白字符 非空白字符 如上所示&am…

二叉树的简单遍历

假设节点数据类如下&#xff1a; public class TreeNode {String val;TreeNode left;TreeNode right;TreeNode() { }TreeNode(String val) {this.val val;}TreeNode(String val, TreeNode left, TreeNode right) {this.val val;this.left left;this.right right;} } 二叉…

Vue3+TS+Vite创建项目,并导入Element-plus和Sass

一、vue3创建项目 1.桌面新建一个文件夹Vue3-app 打开编辑器导入文件夹&#xff0c;编辑器打开终端输入或者命令行工具cd到项目目录下输入 npm init vuelatest 回车运行 这里我选择了TSVite来开发&#xff0c;并选择安装路由 2.cd到 vue-project目录下 输入 npm install 回车…

路径规划算法:基于晶体结构优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于晶体结构优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于晶体结构优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化…

基于高斯混合模型聚类的风电场短期功率预测方法(Pythonmatlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MySQL 学习笔记 1:存储过程

MySQL 学习笔记 1&#xff1a;存储过程 图源&#xff1a;ubiq.co 存储过程可以看做是将一组 SQL打包执行&#xff0c;并返回最终的执行结果。 其优点是&#xff1a;因为存储过程中的 SQL 是同时一次执行&#xff0c;所以没有数据一致性的问题。其次&#xff0c;不需要由客户端…

【Docker】Centos安装docker-compose

下载 直接从GitHub下载docker到本地的/usr/local/bin/目录下&#xff0c;赋予读写权限&#xff0c;检查&#xff0c;就可以使用了&#xff1b; # 下载到/usr/local/bin/docker-compose目录下 sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1…

实现死锁检测组件

文章目录 一、死锁1.1 死锁的定义1.2 死锁产生的条件 二、死锁检测2.1 资源分配图2.2 有向图2.3 死锁检测组件 -- hook 三、死锁设计3.1 有向图3.1.1 数据结构3.1.2 图的基本操作接口3.1.3 判断有向图是否成环 3.2 hook3.2.1 重载系统的锁接口3.2.2 加锁前——lock_before3.2.3…

DevExpress开发练习-皮肤选择

安装devexpress支持&#xff1a; DevExpress 21.2.6 默认状态设置为最大化。 设置ribbon属性&#xff1a; /// <summary> /// 设置Ribbon属性 /// </summary> /// <param name"ribbon"></param> private void SetRibbon(RibbonControl ri…