目录

​1. 概述​

​2. 工程搭建​

​3. Springboot 的启动主流程​

​4. 流程总结​

​5. getSpringFactoriesInstances 方法详解​

​6. 综述​

​7. 个人公众号​


1. 概述

老话说的好:把简单的事情重复做,做到极致,你就成功了。

言归正传,Springboot的启动过程,一直都是面试的高频点,今天我们用当前最新的 Springboot 2.6.2 来聊一聊 Springboot 的启动过程。

2. 工程搭建

2.1 maven 依赖

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

2.2 application.yml 配置文件

server:
port: 30000

spring:
application:
name: my-springboot

2.3 启动类代码

@SpringBootApplication
public class MySpringbootApplication {

public static void main(String[] args) {
SpringApplication.run(MySpringbootApplication.class, args);
}
}

3. Springboot 的启动主流程

3.1 入口

入口当然就是我们 Springboot 启动类中 main 方法里的这段代码,SpringApplication.run 方法

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot

3.2 初始化 SpringApplication 实例

3.2.1 方法总览

我们进入 SpringApplication.run 这个静态方法

从源码角度解析 Springboot 2.6.2 的启动过程_源码_02

 这里调用了 另一个重载的 run 方法,再进

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_03

 此处会 new 一个 SpringApplication 对象,然后调用这个对象的 run 方法

从源码角度解析 Springboot 2.6.2 的启动过程_源码_04

 我们来看一下 SpringApplication 对象实例化时做的事情,这个构造方法调用了另一个重载的构造方法,我们进去看下

从源码角度解析 Springboot 2.6.2 的启动过程_java_05

3.2.2 

this.resourceLoader = resourceLoader;  // resourceLoader 属性注入了 null

3.2.3 

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));  // 将启动类从数组重新封装成了 Set,注入到 primarySources 属性

3.2.4 

this.webApplicationType = WebApplicationType.deduceFromClasspath();  // 得到 web应用的类型,这里是 SERVLET

webApplicationType  有三种类型,REACTIVE、SERVLET、NONE

引入 spring-boot-starter-web 包,就是 SERVLET

引入 spring-boot-starter-webflux 包,是 REACTIVE

都没有就是 NONE

3.2.5 

this.bootstrapRegistryInitializers = new ArrayList<>(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

从 META-INF/spring.factories 文件中得到 key 为 org.springframework.boot.BootstrapRegistryInitializer 的全类名集合,进行实例化,然后注入 bootstrapRegistryInitializers 属性

这里大家先记下 getSpringFactoriesInstances 方法,等下详细介绍

3.2.6

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

从源码角度解析 Springboot 2.6.2 的启动过程_java_06

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_07

这一行代码,只是封装了一下,仍然还是调用 getSpringFactoriesInstances 方法,从 META-INF/spring.factories 文件中得到 key 为

org.springframework.context.ApplicationContextInitializer 的全类名集合,进行实例化,然后注入 initializers(初始化器集合) 属性。

3.2.7 

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));  // 同理,得到监听器实例的集合,并注入

3.2.8

this.mainApplicationClass = deduceMainApplicationClass();  // 获取当前运行的 main 方法所在的类,也就是咱们的主类

3.3 执行 run 方法

3.3.1 方法总览

从源码角度解析 Springboot 2.6.2 的启动过程_java_08

 我们回到这个方法体,进入 run 方法

从源码角度解析 Springboot 2.6.2 的启动过程_spring_09


从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_10

 方法有点长。。。,没关系,我们捡重点看看

3.3.2 

long startTime = System.nanoTime();  // 记录一个开始时间戳

3.3.3 

DefaultBootstrapContext bootstrapContext = createBootstrapContext();  // 添加了一个默认的 Bootstrap 上下文

从源码角度解析 Springboot 2.6.2 的启动过程_spring_11

 从代码看,就是 new 了一个 DefaultBootstrapContext 实例,然后遍历初始化了 bootstrapRegistryInitializers 中的所有初始化器

还记得 bootstrapRegistryInitializers 属性吗,3.2.5 章节中,实例化 SpringApplication 时通过  getSpringFactoriesInstances 方法获得并注入的。

3.3.4

configureHeadlessProperty();  // 配置Headless属性

3.3.5

SpringApplicationRunListeners listeners = getRunListeners(args);  // 获得 RunListener 集合类

从源码角度解析 Springboot 2.6.2 的启动过程_面试_12

这里我们又看到了熟悉的 getSpringFactoriesInstances,这次的 key 是 org.springframework.boot.SpringApplicationRunListener

这里会得到 EventPublishingRunListener 对象

3.3.6

listeners.starting(bootstrapContext, this.mainApplicationClass);  // 循环启动这些监听器

从源码角度解析 Springboot 2.6.2 的启动过程_java_13

从代码看,会调用监听器的 starting 方法,我们看一下 EventPublishingRunListener 对象的 starting 方法

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_14

从源码角度解析 Springboot 2.6.2 的启动过程_java_15

从源码角度解析 Springboot 2.6.2 的启动过程_源码_16

从源码角度解析 Springboot 2.6.2 的启动过程_面试_17

从代码看,在 EventPublishingRunListener 对象的 starting 方法中,做了一个广播,得到应用监听器后,循环调用监听器的 onApplicationEvent 方法

3.3.7 

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);  //  封装参数

3.3.8 

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);  // 创建并配置环境

从源码角度解析 Springboot 2.6.2 的启动过程_spring_18

从源码角度解析 Springboot 2.6.2 的启动过程_java_19

首先会创建环境,因为 webApplicationType 是 SERVLET,因此会创建 ApplicationServletEnvironment 对象

listeners.environmentPrepared(bootstrapContext, environment);

重点是这句

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_20

 listeners.environmentPrepared 方法会执行 EventPublishingRunListener 对象的 environmentPrepared 方法

从源码角度解析 Springboot 2.6.2 的启动过程_面试_21

 来到 EventPublishingRunListener 对象的方法,同样是一个广播,广播给合适的监听器,然后调用监听器的 onApplicationEvent 方法

其中在 EnvironmentPostProcessorApplicationListener 监听器中,会执行拿到所有系统的配置,包括我们在 application.yml 文件中配置的内容。 

我们来看一下 EnvironmentPostProcessorApplicationListener  这个类

从源码角度解析 Springboot 2.6.2 的启动过程_源码_22


从源码角度解析 Springboot 2.6.2 的启动过程_spring_23

 在 EnvironmentPostProcessorApplicationListener  中,会得到环境的处理器,然后循环执行他们

从源码角度解析 Springboot 2.6.2 的启动过程_java_24

 这里可以得到 7 个处理器,其中 ConfigDataEnvironmentPostProcessor 就是加载配置文件得到配置的,我们来看一下这个类的 postProcessEnvironment 方法

从源码角度解析 Springboot 2.6.2 的启动过程_java_25

 在方法中,执行 processAndApply() 方法,最终拿到配置

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_26

 当 listeners.environmentPrepared(bootstrapContext, environment); 最终执行完,我们从 environment 对象中就可以找到我们在 yml 文件中配置的 端口 和 应用名称

3.3.9 

Banner printedBanner = printBanner(environment);  // 打印 Banner

3.3.10

context = createApplicationContext();  // 实例化上下文对象

从源码角度解析 Springboot 2.6.2 的启动过程_面试_27

 因为类型是 SERVLET,所以实例化的是 AnnotationConfigServletWebServerApplicationContext 对象

3.3.11 

prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);  // 准备上下文

从源码角度解析 Springboot 2.6.2 的启动过程_java_28


从源码角度解析 Springboot 2.6.2 的启动过程_源码_29

3.3.12

refreshContext(context);  // 刷新上下文

主要逻辑在 AbstractApplicationContext 对象的 refresh 方法中

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_30


从源码角度解析 Springboot 2.6.2 的启动过程_面试_31

3.3.13

afterRefresh(context, applicationArguments);  // 空方法

3.3.14 

Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);  // 计算耗时

3.3.15

listeners.started(context, timeTakenToStartup);  // 监听器执行  started 方法,表示启动成功

3.3.16

callRunners(context, applicationArguments);  // 回调所有的ApplicationRunner和CommandLineRunner

3.3.17

listeners.ready(context, timeTakenToReady);  // 监听器执行 ready 方法

4. 流程总结

1)实例化 SpringApplication 对象 

2)得到 初始化器 和 监听器

3)调用 run 方法

4)记录开始时间

5)得到 runListeners

6)runListeners 执行 starting

7)准备环境

8)打印 banner

9)实例化上下文对象

10)准备上下文,执行之前得到的初始化器的初始化方法,load主bean

11)刷新上下文,在其中加载 autoConfiguration,并启动 Tomcat

12)计算耗时

13)打印耗时

14)通知监听器启动完成

15)通知监听器 ready

5. getSpringFactoriesInstances 方法详解

从源码角度解析 Springboot 2.6.2 的启动过程_面试_32

从源码角度解析 Springboot 2.6.2 的启动过程_面试_33

 这里面比较关键的逻辑是 得到类的全类名集合 和 实例化类

从源码角度解析 Springboot 2.6.2 的启动过程_spring_34

从源码角度解析 Springboot 2.6.2 的启动过程_源码_35


从源码角度解析 Springboot 2.6.2 的启动过程_spring_36

 从这些代码我们可以得知,会从 META-INF/spring.factories 文件中找到 key 匹配的类,并把类的全路径集合得到

从源码角度解析 Springboot 2.6.2 的启动过程_java_37

例如实例化 SpringApplication 对象时,获得 初始化器 和 监听器

从源码角度解析 Springboot 2.6.2 的启动过程_面试_38

 之后通过全类名,使用反射技术,实例化类,最终得到想要的集合

6. 综述

今天聊了一下 Springboot 2.6.2 的启动过程,希望可以对大家的工作有所帮助

欢迎帮忙点赞、评论、转发、加关注 :)

关注追风人聊Java,每天更新Java干货。

7. 个人公众号

追风人聊Java,欢迎大家关注

从源码角度解析 Springboot 2.6.2 的启动过程_spring boot_39