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