前言

从今天开始我们进入Spring Boot的源码解读系列,本次使用的源码版本为3.2.X,后续默认都用这个版本的源码做解读。

我们先看一个最简单的Spring Boot启动代码:

1
2
3
4
5
6
7
8
@SpringBootApplication
public class SpringbootDemoApplication {

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

}

就这么几行代码就成功启动了一个Spring Boot项目,我们今天就盘一下它是如何启动的。我们从run方法点进去,最终可以看到这样一个方法,总结来说就是先创建一个SpringApplication的实例,然后调用这个实例的run方法。

1
2
3
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}

先看下这个流程图,以对整个流程有个大体的了解。

Spring Boot启动流程

创建SpringApplication实例的过程

这个构造函数的主要作用是创建一个新的SpringApplication实例,并初始化其各种属性和组件。

总得来说,构造SpringApplication实例的时候,会确认应用类型,并从META-INF/spring.factories加载各种属性和组件,然后确认主应用类, 这样就完成了Spring Boot启动的第一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 启动时resourceLoader为null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 将传入的主要源转换为一个集合,保证有序且无重复
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 确认应用类型,SERVLET、REACTIVE或NONE
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/**
* 从META-INF/spring.factories加载所有的BootstrapRegistryInitializer,用于在Spring应用上下文刷新之前初始化应用上
* 下文的环境
*/
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
/**
* 从META-INF/spring.factories加载所有的ApplicationContextInitializer,用于在Spring应用上下文刷新之前初始化应用上
* 下文的环境。
*/
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
/**
* 从META-INF/spring.factories加载所有的ApplicationListener,用于监听Spring应用中的各种事件
*/
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 确认主应用类,这里为SpringbootDemoApplication类
this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication.run()流程

run()是Spring Boot应用启动的主要入口,内部逻辑十分庞大,包括但不限于创建Spring容器,创建bean,启动tomcat等等,都放在这一章去讲无疑是不现实的,所以我们这次只梳理主要流程,并确定run()方法中每步都是用来干什么的。内部方法我们不会深入,后面会开单独的文章去解读他们的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public ConfigurableApplicationContext run(String... args) {
// Startup类主要用于记录应用的启动时间。
Startup startup = Startup.create();
// 是否启用registerShutdownHook,启用时这个hook会在VM关闭时关闭Spring应用上下文。
if (this.registerShutdownHook) {
SpringApplication.shutdownHook.enableShutdownHookAddition();
}
/**
* 创建一个BootstrapContext对象,这个对象用于在Spring Boot启动过程中存储临时数据,如启动过程中创建的基础组件(),需要在应
* 上下文刷新过程中使用到的数据等等(ApplicationContextInitializer等)
*/
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 配置无头模式。无头模式是指是否在没有用户界面的环境中正常运行,例如在服务器或容器中运行。默认是true。
configureHeadlessProperty();
// 获取所有的SpringApplicationRunListener,这些监听器会在Spring Boot启动过程中的各个阶段被调用。
SpringApplicationRunListeners listeners = getRunListeners(args);
/**
* 通知所有的监听器,应用正在启动,所有的SpringApplicationRunListener就有机会在应用启动的最开始阶段执行一些自定义的逻辑,
* 例如初始化一些资源,记录一些日志等
*/
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 构造一个ApplicationArguments实例,用于解析和存储命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
/**
* 环境准备,主要是负责创建和初始化ConfigurableEnvironment对象,包括加载各种属性源(如配置文件、系统环境变量、命令行
* 参数等)到环境对象中,以供后续使用
*/
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 打印banner信息
Banner printedBanner = printBanner(environment);
/**
* 创建Spring上下文,里面主要是设置应用上下文的环境;设置应用上下文的应用参数,这些参数是从命令行参数中解析出来的,包含
* 了用户在启动应用时传入的所有参数。
*/
context = createApplicationContext();
// 设置应用的启动器,用于记录应用的启动过程
context.setApplicationStartup(this.applicationStartup);
/**
* 准备应用上下文。主要作用是将前面准备好的一些数据(如环境、应用参数等)设置到应用上下文中,并通知
* SpringApplicationRunListener应用上下文已经准备好,以便进行后续的处理(如刷新上下文、加载bean定义等)
*/
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
/**
* 刷新应用上下文,这个方法十分重要,因为就是在这里完成了Bean的创建和初始化,并且会启动tomcat操作
*/
refreshContext(context);
// 空实现
afterRefresh(context, applicationArguments);
// 标记应用已经启动。
startup.started();
// 是否打印启动信息到日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startup);
}
/**
* 通知所有的监听器,应用已经启动,这样SpringApplicationRunListener就有机会在应用启动之后执行一些自定义的逻辑,例如打 * 印启动信息、发送启动通知等
*/
listeners.started(context, startup.timeTakenToStarted());
/**
* 调用所有的CommandLineRunner和ApplicationRunner,这俩是一种特殊的bean,它们的run()方法会在Spring应用上下文启动 * 后,且在接收任何应用请求之前被调用,这样就可以中执行一些初始化逻辑,例如初始化数据库、启动后台线程、打印启动信息等
*/
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
}
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
// 如果应用上下文正在运行,那么通知所有的监听器,应用已经准备就绪。
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
catch (Throwable ex) {
if (ex instanceof AbandonedRunException) {
throw ex;
}
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}

其中,refreshContext(context);方法是如此重要,所以我们现在这里看下它的大概逻辑

1
2
3
4
5
6
7
8
9
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}

可以看到,它最后调用的是Spring提供的refresh方法,我们继续看内部逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@Override
public void refresh() throws BeansException, IllegalStateException {
// 加锁,防止多线程同时刷新应用上下文
this.startupShutdownLock.lock();
try {
this.startupShutdownThread = Thread.currentThread();

// 开始记录应用启动过程
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

// 准备刷新应用上下文,例如设置启动日期、激活上下文等
prepareRefresh();

// 获取bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

// 对bean工厂进行一些预处理,例如注册bean后处理器、设置bean类加载器、设置bean表达式解析器等
prepareBeanFactory(beanFactory);

try {
// 允许子类对bean工厂进行后处理,包括添加BeanPostProcessor,设置忽略的自动装配接口,注册Web应用的作用域等
postProcessBeanFactory(beanFactory);

// 启动一个新的应用启动步骤,用于收集关于bean后处理阶段的信息
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");

// 调用所有注册在应用上下文中的bean工厂后处理器
invokeBeanFactoryPostProcessors(beanFactory);

// 注册bean后处理器,这些后处理器可以拦截bean的创建过程
registerBeanPostProcessors(beanFactory);

// 结束记录bean后处理过程
beanPostProcess.end();

// 初始化消息源,用于国际化/本地化
initMessageSource();

// 初始化事件广播器,用于事件发布
initApplicationEventMulticaster();

// 初始化特殊的bean,这个方法由子类实现,启动tomcat就是在这一步
onRefresh();

// 检查并注册监听器
registerListeners();

// 实例化所有剩余的(非延迟初始化)单例bean
finishBeanFactoryInitialization(beanFactory);

// 最后一步:发布相应的事件
finishRefresh();
}

catch (RuntimeException | Error ex ) {
// 如果在刷新过程中遇到异常,那么记录警告日志
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}

// 销毁已经创建的单例bean,避免资源泄漏
destroyBeans();

// 重置'active'标志
cancelRefresh(ex);

throw ex;
}

finally {
// 结束记录应用启动过程
contextRefresh.end();
}
}
finally {
this.startupShutdownThread = null;
// 释放锁
this.startupShutdownLock.unlock();
}
}

@SpringBootApplication的作用

在Spring Boot的启动类中我们会发现它被标注了一个@SpringBootApplication,那么它是用来干什么的?简单来说,它只是一个组合注解,但是它内部三个核心注解却是Spring Boot启动过程中扮演着不可或缺的角色。

@SpringBootConfiguration

@SpringBootConfiguration继承了@Configuration,所以它拥有@Configuration注解的所有特性。这个注解的作用主要是表示当前类是一个配置类。

@ComponentScan

它的作用是启动组件扫描,然后Spring就会知道需要在哪些包以及子包中进行扫描。

@EnableAutoConfiguration

它的主要作用是启动Spring Boot的自动配置机制,在它内部还有两个重要功能,他们都是自动配置的一部分

@AutoConfigurationPackage

它用于标记一个包为自动配置包。当你在一个类上添加@AutoConfigurationPackage注解时,Spring Boot会自动扫描这个类所在的包以及子包,查找所有的配置类,并将这些类作为自动配置的候选者。

AutoConfigurationImportSelector类

这是一个ImportSelector的实现,它的主要作用是选择所有需要自动配置的类。它会读取META-INF/spring.factories文件,查找所有的自动配置类,然后根据条件选择需要导入的类。这些类会被Spring框架自动注册到Spring上下文中,然后根据这些类的定义创建和初始化bean。