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

news2024/11/18 7:29:34

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就运行完成了。
感谢观看。

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

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

相关文章

J.砍树【蓝桥杯】树上差分+LCA

树上差分 多次对树上的一些路径做加法操作&#xff0c;然后询问某个点或某条边经过操作后的值&#xff0c;就要考虑树上差分了。 点差分 模拟这个过程 对x到y路径上的点权值均1&#xff0c;可以等价成对x和y的权值加1&#xff0c;对lca的权值-1&#xff0c;对fa[lca]的权值-…

操作系统知识-存储管理+文件管理管理-嵌入式系统设计师备考笔记

0、前言 本专栏为个人备考软考嵌入式系统设计师的复习笔记&#xff0c;未经本人许可&#xff0c;请勿转载&#xff0c;如发现本笔记内容的错误还望各位不吝赐教&#xff08;笔记内容可能有误怕产生错误引导&#xff09;。 本章的主要内容见下图&#xff1a; 1、存储管理&#…

【数据结构取经之路】归并排序

简介 归并排序是建立在归并操作上的一种有效&#xff0c;稳定的排序算法&#xff0c;该算法是采用分治法&#xff08;Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有序&#xff0c;…

在Docker上传我们自己的镜像(以springboot项目为例)

首先确定好在我们的centOS服务器上已经安装并配置好docker 配置自己的springboot镜像并运行 获取springboot的jar包 maven clean--》mavenue package --》复制target目录下生成的jar包 在服务器选择一个文件夹上传jar包&#xff0c;我这里选用的文件夹叫做/opt/dockertest…

如何在HomeAssistant智能家居系统中添加HACS集成并实现无公网IP远程连接家中设备

文章目录 基本条件一、下载HACS源码二、添加HACS集成三、绑定米家设备 ​ 上文介绍了如何实现群晖Docker部署HomeAssistant&#xff0c;通过内网穿透在户外控制家庭中枢。本文将介绍如何安装HACS插件商店&#xff0c;将米家&#xff0c;果家设备接入 Home Assistant。 基本条件…

Python the code is unreachable

Python the code is unreachable 正文 正文 相信有不少小伙伴在使用 Python 的时候有时候会遇到 the code is unreachable 这样的 warning 提示。这种提示表示在我们当前书写的代码种有一部分代码被屏蔽了。可能会存在潜在的 bug&#xff0c;需要我们注意&#xff0c;那么什么…

2023年蓝桥杯省赛——幸运数字

目录 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 思路 高级思路 总结 题目链接&#xff1a;0幸运数字 - 蓝桥云课 (lanqiao.cn) 解法 首先是我写了差不多一个小时的解法&#xff0c;裂开了&#xff0c;为什么我如此废物 思路 寻找第2023个在二进制、八…

弱电工程是什么?常见的类型有哪些?

一、什么是弱电工程?强电和弱电的区别有哪些? 弱电工程又叫智能建筑&#xff0c;也叫系统集成工程&#xff0c;所有与信息有关的都属于弱电这一块的。弱电是相对于强电而言的强电和弱电从概念上讲&#xff0c;一般是容易区别的&#xff0c;主要区别是用途的不同。强电是用作…

Compute Express Link (CXL): An Open Interconnect for Cloud Infrastructure——论文阅读

DAC 2023 Paper CXL论文阅读笔记整理 背景 Compute Express Link是一种开放的行业标准互连&#xff0c;在PCI Express&#xff08;PCIe&#xff09;之上提供缓存和内存语义&#xff0c;具有资源池和织物功能。本文探讨了CXL在解决云基础设施中的一些挑战方面的作用。 CXL主要…

Python:文件的操作

注意&#xff1a;本文引用自专业人工智能社区Venus AI 更多AI知识请参考原站 &#xff08;[www.aideeplearning.cn]&#xff09; Python的os库主要用于与操作系统进行交互&#xff0c;它提供了多种功能&#xff0c;使得在Python程序中处理操作系统级任务变得容易。这里是一些…

外包干了3个月,技术明显进步。。。。。

在湖南的一个安静角落&#xff0c;我&#xff0c;一个普通的大专生&#xff0c;开始了我的软件测试之旅。四年的外包生涯&#xff0c;让我在舒适区里逐渐失去了锐气&#xff0c;技术停滞不前&#xff0c;仿佛被时间遗忘。然而&#xff0c;生活的转机总是在不经意间降临。 与女…

1.MongoDB的特点与应用场景

什么是 MongoDB &#xff1f; MongoDB 是基于 C 开发的 NOSQL 开源文档数据库 &#xff0c;是最像关系型数据库的 nosql&#xff0c;功能也是最丰富的 nosql&#xff0c;它具有所以的可伸缩性&#xff0c;灵活性&#xff0c;高性能&#xff0c;高扩展性的优势。 大致有如下特…

Vulnhub - Morpheus

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

代码随想录算法训练营第二十四天|● 理论基础 ● 77. 组合(JS写法)

回溯理论基础 回溯法解决的问题都可以抽象为树形结构&#xff0c;因为回溯法解决的都是在集合中递归查找子集&#xff0c;集合的大小就构成了树的宽度&#xff0c;递归的深度&#xff0c;都构成的树的深度。递归就要有终止条件&#xff0c;所以必然是一棵高度有限的树&#xff…

一篇搞定ECharts的基本使用,赶快收藏起来学习吧~

准备工作 引入 声明一个有宽高的dDOM元素 echarts.init(DOM) option配置对象 echarts.setOptions(option) 基础配置 option类似于一个容器&#xff0c;那么里面的属性就相当于组件&#xff1a; xAxis&#xff08;直角坐标系 X 轴&#xff09;、yAxis&#xff08;直角坐…

关于udp能跨局域网传输的问题

UDP&#xff08;用户数据报协议&#xff09;以其独特的传输特性在多种应用场景中都有着极其重要的作用。然而&#xff0c;关于UDP是否能跨局域网&#xff08;LAN&#xff09;进行传输&#xff0c;以及这一传输过程中的优缺点&#xff0c;一直是网络技术领域讨论的热点。本文将详…

git基础命令(四)之分支命令

目录 基础概念git branch-r-a-v-vv-avv重命名分支删除分支git branch -h git checkout创建新的分支追踪远程分支同时切换到该分支创建新的分支并切换到该分支撤销对文件的修改&#xff0c;恢复到最近的提交状态&#xff1a;丢弃本地所有修改git checkout -h git merge合并指定分…

Windows系统安装VNC客户端结合内网穿透实现公网远程连接Deepin桌面

文章目录 1. 安装x11vnc2. 本地远程连接测试3. Deepin安装Cpolar4. 配置公网远程地址5. 公网远程连接Deepin桌面6. 固定连接公网地址7. 固定公网地址连接测试 x11vnc是一种在Linux系统中实现远程桌面控制的工具&#xff0c;它的原理是通过X Window系统的协议来实现远程桌面的展…

1688商品详情API接口采集商品上货

阿里巴巴1688平台并没有直接公开商品详情API接口供普通用户或开发者进行商品采集和上货。1688平台主要服务于批发和采购业务&#xff0c;其API服务通常面向的是有深度合作关系的商家或开发者&#xff0c;且需要经过申请和审核流程。 请求示例&#xff0c;API接口接入Anzexi58 …

Python Web开发记录 Day14:Django part8 订单管理

名人说&#xff1a;莫道桑榆晚&#xff0c;为霞尚满天。——刘禹锡&#xff08;刘梦得&#xff0c;诗豪&#xff09; 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 1、数据库准备2、添加订单3、订单列表4、删除订…