一文教会你SpringBoot是如何启动的

SpringBoot启动流程分析

流程图

未命名文件 (2).png

源码剖析

运行Application.run()方法

我们在创建好一个 SpringBoot 程序之后,肯定会包含一个类:xxxApplication,我们也是通过这个类来启动我们的程序的(梦开始的地方),而这个启动类中代码如下:
image.png
可以看到这里的代码非常的简洁,一个 main方法,在该方法中调用了 SpringApplication.run() 方法,我们也可以去看一下里面的实现。
image.png
这里的run方法接收了两个参数,一个是名为 primarySource 的类,另一个是 args 参数,其中最为主要的也就是 primarySource 参数,该参数接收了我们要启动的是哪一个类,我们把滚动条拉到最上面可以看到一个构造函数:
image.png
在这个构造函数里将我们的启动类添加到了一个 LinkedHashSet中,而在它的下面有一个 webApplicationType 参数,这就是我们用来确定应用程序类型的地方。

SpringApplication构造函数

确定应用程序类型

我们去看一下 WebApplicationType.deduceFromClasspath() 方法的实现逻辑:
image.png在这里我们确定了应用程序的容器,依照上面的代码我们可以看出来一共有三种类型:Servlet(默认)Reactive(响应式编程)None

加载所有的初始化器

我们回到 SpringApplication 类的构造器中,其中有一个 this.setInitializers()方法,用来设置我们的初始化器。
image.png
而我们的初始化器是通过扫描 META-INF/spring.factories 来知道需要加载哪些初始化器的,我们也可以去点开IDEA中的SpringBoot的jar包,我们可以看到其在 META-INF 文件夹下有一个名为 spring.factories的文件。
image.png
我们点开这个文件可以发现里面是一个又一个的全限定名,而其中有一个 ApplicationContextInitializer 的全限定名,此处就是定义初始化器的地方:
image.png
我们随便点击一个进去后发现,其实现了 ApplicationContextInitializer<ConfigurableApplicationContext>中的 initialize() 方法。
那么我们也试试能不能通过他这种写法来写一个初始化器

自定义初始化器

首先我们定义一个类来实现 ApplicationContextInitializer<ConfigurableApplicationContext>,并重写一下 initialize()方法
image.png
接着我们在 resource 目录下创建一个名为 META-INF的文件夹,并在文件夹中创建一个名为 spring.factories的文件
image.png
再在其中写上我们的初始化器的全限定名即可
image.png
接着我们启动我们的应用发现我们的打印是正常的
image.png

加载所有的监听器

同样的,我们来到SpringBoot的jar包中的spring.factories 文件中,在初始化器的下方有个 ApplicationListener,我们通过名字可以猜到,这里是定义要加载的监听器的地方
image.png
我们随便点击一个进去发现,他们和初始化器一样,都实现了一个类,监听器的类为 ApplicationListener<ContextRefreshedEvent>
image.png
我们也来试试能不能写一个自定义的监听器给加载上。

自定义监听器

首先定义一个类来实现ApplicationListener 中的 onApplicationEvent()方法
image.png
再在spring.factories中来定义一下我们要加载的监听器
image.png
接着我们启动一下项目,可以看到我们的监听器成功被加载了,并且也在初始化器的后面
image.png

设置程序运行的主类

我们重新回到 SpringApplication中的构造器中,而其中的最后一行就是去设置我们程序运行的主类
image.png
而我们点入方法看一眼
image.png
我们看代码可以看到,他在寻找方法名为 main的类,并且将其返回出去,也就是说我们的程序是通过这个方法来推断我们程序的主类在哪里的。
至此,我们构造函数就执行完毕了,接下来就会进入到run方法中来运行我们的程序。

run() 方法

开启计时器

其实在原先的版本中是开启计时器,但是在新版本中使用的是通过 System.nanoTime()互减的方法来实现计时的,如下:
image.png
其最主要的作用是来计算程序启动过程中使用的时长

启用Headless模式

在run方法中我们可以看到执行了 this.configureHeadlessProperty()的方法
image.png
我们来到这个方法体中,可以看到这里是用来获取 java.awt.headless
image.png
其目的是为了让程序可以在没有显示器和鼠标的情况下也可以正常工作,用来模拟输入和输出设备

获取并开启监听器

在我们开启了 Headless 模式 后,程序获取了监听器,并将其开启了,程序如下:
image.png
那么其是如何获取监听器的呢?
image.png点进来后我们可以发现,他是通过加载 spring.factories的配置来获取到所有的监听器的,也就是刚才我们说的地方

设置应用程序参数

image.png
在这里程序是使用了默认的参数配置,如下:
image.png
此处的 args 就是我们的程序入口传入的args
image.png

准备环境变量

当我们的应用程序参数设置完成后,程序会开始准备环境变量
image.png
我们进入到 this.prepareEnvironment() 的方法体中,并在最后返回的地方打个断点
image.png
可以看到我们的环境变量均被加载进来了

忽略Bean信息

这里是将 spring.beaninfo.ignore的值设置为true,没什么好说的,原理和上面 启用Headless模式 一样
image.png

image.png

打印Banner信息

image.png
我们在这个地方来打印程序的banner,也就是我们程序运行时打印的logo
image.png
它是有一个默认值的,定义在SpringBootBanner
image.png
我们想要更改时只需要在 resource 下创建一个名为 banner.txt 的文件即可
image.png
最后我们启动就可以得到如下的输出
image.png

创建程序上下文

程序通过执行 createApplicationContext() 方法来进行创建程序的上下文对象
image.png
此处就是利用反射来创建对象

实例化异常报告器

当我们启动出错时会被捕获异常,并且执行一个名为 handleRunFailure() 的方法
image.png
我们点进去可以看到其中有一个 getExceptionReporters() 的方法
image.png
image.png
我们可以清晰的看到上面调用了getSpringFactoriesInstances()方法,此处就是在我们的 spring.factories中获取参数的方法,上面我们也提到过很多次,这里就不过多赘述了。
image.png
我们点进去发现其也是实现了一个类并重写其中的方法
image.png
那么我们也去定义一个自己的异常报告器来试试

自定义异常报告器

首先我们要先创建一个类来实现 SpringBootExceptionReporter 中的 onApplicationEvent()方法
image.png
然后在 spring.factories中定义即可
image.png
但是需要注意的是,我们的程序在执行不出错的情况下,异常报告器是不会执行的,所以我们要手动制造一个错误来使其报错。那么我们就在加载我们自定义的监听器时主动抛出一个异常。
image.png
接着我们运行程序就会得到以下结果:
image.png

准备上下文

此处程序执行了一个名为 prepareContext() 的方法
image.png
我们到方法体内可以得到如下代码:
image.png
其中比较重要的就是 postProcessApplicationContext()applyInitializers()beanFactory.registerSingleton("springApplicationArguments", applicationArguments)这三个方法,接下来我们逐一去分析

postProcessApplicationContext()

方法体如下:
image.png
其中最主要的就是这个 beanNameGenerator ,也就是 Bean名称生成器,主要的作用就是用来 创建Bean对象的名称

applyInitializers()

image.png
通过以上方法体,我们可以看到,其中有一个迭代器用于遍历,并且均执行了 initializer.initialize(context 方法,此处也就是我们的初始化方法。
那么该方法的作用就是来执行所有的初始化方法,而我们程序中的初始化器在 SpringBoot构造函数 阶段就已经加载完毕了,其实实质就是来执行我们所有的初始化器中的初始化方法。

beanFactory.registerSingleton()

image.png
此处的代码我们就更好理解了,其创建了一个 Bean工厂,并且以单例模式注册了一个东西,那么是什么呢?
没错,就是名为springApplicationArguments 的参数,但是我们英语水平不够,不知道它是什么意思怎么办?
没关系,科技使人进步,我们还有翻译软件ヾ(≧▽≦*)o
image.png
没错,是应用程序参数!我们将启动的参数以单例模式注册到我们的容器中,其目的是为了方便之后的读取使用。

刷新上下文

image.png
这里就是单纯的刷新上下文,我们之前学习的自动装配和Tomcat的启动就是在这里完成的

刷新上下文的后置处理

image.png
这里是启动后的一些处理,暂时这个方法是空的,留给用户自定义。
既然如此,那为什么不来点自定义的 afterRefresh()尝尝鲜呢o( ̄▽ ̄)ブ
首先我们要自定义一个类来继承 SpringApplication 并重写其中的 afterRefresh 方法
image.png
接着我们要去改造我们的程序入口
image.png
最后我们启动我们的程序就可以得到:
image.png

结束计时器

在老版本中我们是使用 stopWatch来完成计时器的功能的,前面也讲了,在新版本中我们是使用时间戳互减来完成我们计时的功能的
image.png

发布上下文准备就绪事件

image.png
其目的就是告诉应用程序:嘿哥们儿,我准备好了,咱们可以开始工作了。

执行自定义的run()方法

image.png
在此处我们可以看到,其加载了两个类型的run方法,一种是 ApplicationRunner,另一种是 CommandLineRunner,该方法将这两种类型的所有runnner都添加到一个 ArrayList 中,并进行排序。
在排序完成后就由迭代器来逐一执行runner的 callRunner() 方法。
那么我们也可以自定义我们的runner来使其执行。
来吧老伙计,都最后一个步骤了,跟着我一起实现一下。
image.png
只需要自定义类并实现 ApplicationRunnerCommandLineRunner并重写其中的 run()方法即可,此处为了同时演示两种方法,我就同时实现了两个类型,大家可以根据实际情况来选择。
最后我们运行程序就可以得到:
image.png
至此,我们的SpringBoot就运行完成了。
感谢观看。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【超详细图文讲解】如何利用VMware创建CentOS虚拟机(包括如何更改网络设置 + 远程访问虚拟机方法)

文章目录 前言1. 准备相关软件环境1.1 获取 ISO 镜像包1.2 VMware 的安装 2. 使用 VMware 安装 CentOS3. 初始化虚拟机4. 虚拟机网络的设置4.1 虚拟机的三种网络连接模式桥接模式NAT 模式仅主机模式 4.2 如何更改网络设置 5. 远程访问虚拟机的方法5.1 使用 cmd 进行访问5.2 使用…

LSS (Lift, Splat, Shoot)

项目主页 https://nv-tlabs.github.io/lift-splat-shoot 图1&#xff1a;本文提出一种模型&#xff0c;给定多视角相机数据 (左)&#xff0c; 直接在鸟瞰图 (BEV) 坐标系(右)中推理语义。我们展示了车辆分割 (蓝色)&#xff0c;可驾驶区域 (橙色) 和车道分割 (绿色) 的结果。然…

外包干了28天,技术退步明显......

说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入深圳某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试&a…

<DFS剪枝>数字王国之军训排队

DFS剪枝 其实就是将搜索过程一些不必要的部分直接剔除掉。 剪枝是回溯法的一种重要优化手段&#xff0c;往往需要先写一个暴力搜索&#xff0c;然后找到某些特殊的数学关系&#xff0c;或者逻辑关系&#xff0c;通过它们的约束让搜索树尽可能浅而小&#xff0c;从而达到降低时间…

程序员在公司学习新项目的5步法:

1 了解业务 - 系统所在行业&#xff1f; - 系统是做什么的&#xff1f; - 系统主要面向的人群是谁&#xff1f; - 主要提供了哪些功能&#xff1f; - 系统设计的关键业务流程是什么样的&#xff1f; - 项目面临的挑战是什么&#xff1f; - 项目未来规划是什么&#xff1f; 2 …

HarmonyOS(鸿蒙)快速入门

一:下载开发工具 鸿蒙的开发工具叫DevEco 下载点击 其他部分都一直next 就行,这个页面出现的install 建议都点击install 然后单独选择安装目录 可能存在的问题 就是之前安装nodejs&#xff08;比如自己开发web或者RN等情况&#xff09;版本低 等情况 所以建议你单独安装一次 …

c语言商品库存管理系统

定制魏:QTWZPW,获取更多源码等 目录 题目 功能概述 数据结构 用户界面 ​编辑 主要函数 数据存储 完整代码 总结 题目 实现一个商品库存管理系统,可以对商品进行入库、出库、删除、修改、查询以及显示所有商品信息的操作。 功能概述 系统包含以下主要功能: 商品…

Web基础06-AJAX,Axios,JSON数据

目录 一、AJAX 1.概述 2.主要作用 3.快速入门 4.AJAX的优缺点 &#xff08;1&#xff09;优点 &#xff08;2&#xff09;缺点 5.同源策略 二、Axios 1.概述 2.快速入门 3.请求方式别名 三、JSON 1.概述 2.主要作用 3.基础语法 4.JSON数据转换 &#xff08;1…

【MLLM+轻量多模态模型】24.02.Bunny-v1.0-2B-zh: 轻量级多模态语言模型 (效果一般)

24.02 北京人工智能研究院&#xff08;BAAI&#xff09;提出以数据为中心的轻量级多模态模型 arxiv论文&#xff1a;2402.Efficient Multimodal Learning from Data-centric Perspective 代码&#xff1a;https://github.com/BAAI-DCAI/Bunny 在线运行&#xff1a;https://wis…

day6 3/18

2.试编程&#xff1a; 封装一个动物的基类&#xff0c;类中有私有成员&#xff1a;姓名&#xff0c;颜色&#xff0c;指针成员年纪 再封装一个狗这样类&#xff0c;共有继承于动物类&#xff0c;自己拓展的私有成员有&#xff1a;指针成员&#xff1a;腿的个数&#xff08;整…

【JavaEE -- 多线程进阶 - 面试重点】

多线程进阶 1. 常见锁策略1.1 乐观锁和悲观锁1.2 轻量级锁和重量级锁1.3 自旋锁和挂起等待锁synchronized具有自适应能力1.4 普通互斥锁和读写锁1.5 公平锁和非公平锁1.6 可重入锁和不可重入锁 2. Synchronized原理&#xff08;特点、加锁过程、自适应&#xff09;2.1 Synchron…

数据结构(三)——栈

三、栈、队列和数组 3.1 栈 3.1.1 栈的基本概念 线性表是具有相同数据类型的n&#xff08;n≥0&#xff09;个数据元素的有限 序列&#xff0c;其中n为表长&#xff0c;当n 0时线 性表是一个空表。若用L命名线性表&#xff0c;则其一般表示为 L (a1, a2, … , ai , ai1, ……

【STL源码剖析】【2、空间配置器——allocator】

文章目录 1、什么是空间配置器&#xff1f;1.1设计一个简单的空间配置器&#xff0c;JJ::allocator 2、具备次配置力( sub-allocation)的 SGI 空间配置器2.1 什么是次配置力2.2 SGI标准的空间配置器&#xff0c;std::allocator2.2 SGI特殊的空间配置器&#xff0c;std::alloc2.…

ARM汇编与逆向工程 蓝狐卷 基础知识

文章目录 前言内容简介作者简介译者简介目录 前言 与传统的CISC&#xff08;Complex Instruction Set Computer&#xff0c;复杂指令集计算机&#xff09;架构相比&#xff0c;Arm架构的指令集更加简洁明了&#xff0c;指令执行效率更高&#xff0c;能够在更低的功耗下完成同样…

13 秒插入 30 万条数据,这才是 Java 批量插入正确的姿势!

本文主要讲述通过MyBatis、JDBC等做大数据量数据插入的案例和结果。 30万条数据插入插入数据库验证 实体类、mapper和配置文件定义 User实体 mapper接口 mapper.xml文件 jdbc.properties sqlMapConfig.xml 不分批次直接梭哈 循环逐条插入 MyBatis实现插入30万条数据 J…

python redis中blpop和lpop的区别

python redis中lpop()方法是获取并删除左边第一个对象。 def lpop(self,name: str,count: Optional[int] None,) -> Union[Awaitable[Union[str, List, None]], Union[str, List, None]]:"""Removes and returns the first elements of the list name.By de…

2258: 【搜索】【广度优先】最少转弯问题

题目描述 给出一张地图&#xff0c;这张地图被分为nm&#xff08;n,m<100&#xff09;个方块&#xff0c;任何一个方块不是平地就是高山。平地可以通过&#xff0c;高山则不能。现在你处在地图的&#xff08;x1,y1&#xff09;这块平地&#xff0c;问&#xff1a;你至少需要…

Vulnhub - Morpheus

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog Morpheus 靶机下载地址&#xff1a;Matrix-Breakout: 2 Morpheus ~ VulnHub 0x01 信息收集 Nmap扫描…

Spring学习记录

为什么要学Spring&#xff1f; 在回答这个问题时&#xff0c;我们先来看看现在的Java程序是如何实现的&#xff0c;以最简单的服务层与持久层为例&#xff0c;其遵循接口与具体实现类的这种方式&#xff1a; Service层接口&#xff1a;BookService.java package service; pu…

mysql笔记:22. 事务隔离级别的一种通俗讲解

事务隔离级别&#xff0c;是为了解决多个并行事务竞争导致的数据安全问题的一种规范。具体来说&#xff0c;多个事务竞争可能会产生三种不同的现象。假设有两个事务T1、T2同时执行&#xff0c;有如下三种不同的情形&#xff1a; T1可能会读到T2未提交的数据&#xff0c;但是未…