SpringBoot启动流程分析
流程图

源码剖析
运行Application.run()方法
我们在创建好一个 SpringBoot 程序之后,肯定会包含一个类:xxxApplication,我们也是通过这个类来启动我们的程序的(梦开始的地方),而这个启动类中代码如下:

可以看到这里的代码非常的简洁,一个 main方法,在该方法中调用了 SpringApplication.run() 方法,我们也可以去看一下里面的实现。

这里的run方法接收了两个参数,一个是名为 primarySource 的类,另一个是 args 参数,其中最为主要的也就是 primarySource 参数,该参数接收了我们要启动的是哪一个类,我们把滚动条拉到最上面可以看到一个构造函数:

在这个构造函数里将我们的启动类添加到了一个 LinkedHashSet中,而在它的下面有一个 webApplicationType 参数,这就是我们用来确定应用程序类型的地方。
SpringApplication构造函数
确定应用程序类型
我们去看一下 WebApplicationType.deduceFromClasspath() 方法的实现逻辑:
在这里我们确定了应用程序的容器,依照上面的代码我们可以看出来一共有三种类型:Servlet(默认)、Reactive(响应式编程) 和 None。
加载所有的初始化器
我们回到 SpringApplication 类的构造器中,其中有一个 this.setInitializers()方法,用来设置我们的初始化器。

而我们的初始化器是通过扫描 META-INF/spring.factories 来知道需要加载哪些初始化器的,我们也可以去点开IDEA中的SpringBoot的jar包,我们可以看到其在 META-INF 文件夹下有一个名为 spring.factories的文件。

我们点开这个文件可以发现里面是一个又一个的全限定名,而其中有一个 ApplicationContextInitializer 的全限定名,此处就是定义初始化器的地方:

我们随便点击一个进去后发现,其实现了 ApplicationContextInitializer<ConfigurableApplicationContext>中的 initialize() 方法。
那么我们也试试能不能通过他这种写法来写一个初始化器
自定义初始化器
首先我们定义一个类来实现 ApplicationContextInitializer<ConfigurableApplicationContext>,并重写一下 initialize()方法

接着我们在 resource 目录下创建一个名为 META-INF的文件夹,并在文件夹中创建一个名为 spring.factories的文件

再在其中写上我们的初始化器的全限定名即可

接着我们启动我们的应用发现我们的打印是正常的

加载所有的监听器
同样的,我们来到SpringBoot的jar包中的spring.factories 文件中,在初始化器的下方有个 ApplicationListener,我们通过名字可以猜到,这里是定义要加载的监听器的地方

我们随便点击一个进去发现,他们和初始化器一样,都实现了一个类,监听器的类为 ApplicationListener<ContextRefreshedEvent>

我们也来试试能不能写一个自定义的监听器给加载上。
自定义监听器
首先定义一个类来实现ApplicationListener 中的 onApplicationEvent()方法

再在spring.factories中来定义一下我们要加载的监听器

接着我们启动一下项目,可以看到我们的监听器成功被加载了,并且也在初始化器的后面

设置程序运行的主类
我们重新回到 SpringApplication中的构造器中,而其中的最后一行就是去设置我们程序运行的主类

而我们点入方法看一眼

我们看代码可以看到,他在寻找方法名为 main的类,并且将其返回出去,也就是说我们的程序是通过这个方法来推断我们程序的主类在哪里的。
至此,我们构造函数就执行完毕了,接下来就会进入到run方法中来运行我们的程序。
run() 方法
开启计时器
其实在原先的版本中是开启计时器,但是在新版本中使用的是通过 System.nanoTime()互减的方法来实现计时的,如下:

其最主要的作用是来计算程序启动过程中使用的时长
启用Headless模式
在run方法中我们可以看到执行了 this.configureHeadlessProperty()的方法

我们来到这个方法体中,可以看到这里是用来获取 java.awt.headless值

其目的是为了让程序可以在没有显示器和鼠标的情况下也可以正常工作,用来模拟输入和输出设备
获取并开启监听器
在我们开启了 Headless 模式 后,程序获取了监听器,并将其开启了,程序如下:

那么其是如何获取监听器的呢?
点进来后我们可以发现,他是通过加载 spring.factories的配置来获取到所有的监听器的,也就是刚才我们说的地方
设置应用程序参数

在这里程序是使用了默认的参数配置,如下:

此处的 args 就是我们的程序入口传入的args

准备环境变量
当我们的应用程序参数设置完成后,程序会开始准备环境变量

我们进入到 this.prepareEnvironment() 的方法体中,并在最后返回的地方打个断点

可以看到我们的环境变量均被加载进来了
忽略Bean信息
这里是将 spring.beaninfo.ignore的值设置为true,没什么好说的,原理和上面 启用Headless模式 一样


打印Banner信息

我们在这个地方来打印程序的banner,也就是我们程序运行时打印的logo

它是有一个默认值的,定义在SpringBootBanner 中

我们想要更改时只需要在 resource 下创建一个名为 banner.txt 的文件即可

最后我们启动就可以得到如下的输出

创建程序上下文
程序通过执行 createApplicationContext() 方法来进行创建程序的上下文对象

此处就是利用反射来创建对象
实例化异常报告器
当我们启动出错时会被捕获异常,并且执行一个名为 handleRunFailure() 的方法

我们点进去可以看到其中有一个 getExceptionReporters() 的方法


我们可以清晰的看到上面调用了getSpringFactoriesInstances()方法,此处就是在我们的 spring.factories中获取参数的方法,上面我们也提到过很多次,这里就不过多赘述了。

我们点进去发现其也是实现了一个类并重写其中的方法

那么我们也去定义一个自己的异常报告器来试试
自定义异常报告器
首先我们要先创建一个类来实现 SpringBootExceptionReporter 中的 onApplicationEvent()方法

然后在 spring.factories中定义即可

但是需要注意的是,我们的程序在执行不出错的情况下,异常报告器是不会执行的,所以我们要手动制造一个错误来使其报错。那么我们就在加载我们自定义的监听器时主动抛出一个异常。

接着我们运行程序就会得到以下结果:

准备上下文
此处程序执行了一个名为 prepareContext() 的方法

我们到方法体内可以得到如下代码:

其中比较重要的就是 postProcessApplicationContext()、applyInitializers()和 beanFactory.registerSingleton("springApplicationArguments", applicationArguments)这三个方法,接下来我们逐一去分析
postProcessApplicationContext()
方法体如下:

其中最主要的就是这个 beanNameGenerator ,也就是 Bean名称生成器,主要的作用就是用来 创建Bean对象的名称
applyInitializers()

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

此处的代码我们就更好理解了,其创建了一个 Bean工厂,并且以单例模式注册了一个东西,那么是什么呢?
没错,就是名为springApplicationArguments 的参数,但是我们英语水平不够,不知道它是什么意思怎么办?
没关系,科技使人进步,我们还有翻译软件ヾ(≧▽≦*)o

没错,是应用程序参数!我们将启动的参数以单例模式注册到我们的容器中,其目的是为了方便之后的读取使用。
刷新上下文

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

这里是启动后的一些处理,暂时这个方法是空的,留给用户自定义。
既然如此,那为什么不来点自定义的 afterRefresh()尝尝鲜呢o( ̄▽ ̄)ブ
首先我们要自定义一个类来继承 SpringApplication 并重写其中的 afterRefresh 方法

接着我们要去改造我们的程序入口

最后我们启动我们的程序就可以得到:

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

发布上下文准备就绪事件

其目的就是告诉应用程序:嘿哥们儿,我准备好了,咱们可以开始工作了。
执行自定义的run()方法

在此处我们可以看到,其加载了两个类型的run方法,一种是 ApplicationRunner,另一种是 CommandLineRunner,该方法将这两种类型的所有runnner都添加到一个 ArrayList 中,并进行排序。
在排序完成后就由迭代器来逐一执行runner的 callRunner() 方法。
那么我们也可以自定义我们的runner来使其执行。
来吧老伙计,都最后一个步骤了,跟着我一起实现一下。

只需要自定义类并实现 ApplicationRunner 或 CommandLineRunner并重写其中的 run()方法即可,此处为了同时演示两种方法,我就同时实现了两个类型,大家可以根据实际情况来选择。
最后我们运行程序就可以得到:

至此,我们的SpringBoot就运行完成了。
感谢观看。