SpringBoot启动流程

大多数情况下,我们都可以在 main() 方法中调用 SpringApplication.run() 引导启动一个SpringBoot应用。

1
2
3
4
5
6
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(MyApplication.class, args);
}
}

默认情况下,SpringApplication 将执行以下步骤来引导应用程序:

  • 根据 classpath 创建一个合适的 ApplicationContext 实例
  • 注册一个 CommandLinePropertySource 以将命令行参数公开为 Spring 属性
  • 刷新应用程序上下文,加载所有单例 Bean
  • 触发所有 CommandLineRunner

当然,真实的应用场景会比默认情况复杂的多,下面我们通过分析 SpringApplication.run() 的实现,以对整个流程有一个更加详细的了解。

1
2
3
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}

在 SpringApplication.run() 里面,启动流程主要分为如下两个步骤:

  1. 创建 SpringApplication 对象,并完成初始化操作
  2. 调用 run() 方法启动应用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
// 添加Bean的配置源,后面由 BeanDefinitionLoader 进行加载
this.sources.addAll(Arrays.asList(sources));
}
// 检测是否为Web应用环境
this.webEnvironment = deduceWebEnvironment();
// 加载 spring.factories 中配置的 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 加载 spring.factories 中配置的 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 通过线程栈信息查询 main 方法所在类
this.mainApplicationClass = deduceMainApplicationClass();
}

正式启动前的初始化流程主要包括如下几个步骤:

  1. 添加特定的 Bean 配置源,在启动过程中由 BeanDefinitionLoader 加载并注册到 BeanFactory,在创建 SpringApplication 的过程中进行指定;
  2. 检测是否为Web环境,主要依据 javax.servlet.Servlet 及 org.springframework.web.context.ConfigurableWebApplicationContext 是否可以成功被类加载器加载;
  3. 利用SPI机制从 spring.factories 加载 ApplicationContextInitializer 及 ApplicationListener 实现类,由 SpringFactoriesLoader.loadFactoryNames() 进行配置读取,配置的 key 为接口或抽象类的完全限定名;
  4. 查找main方法所在类,通过模拟异常栈信息进行方法名匹配;
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
public ConfigurableApplicationContext run(String... args) {
// StopWatch 用来统计启动流程耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
// 配置 awt 的 headless 属性
configureHeadlessProperty();
// 加载 SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
// 通过调用 SpringApplicationRunListener 的 starting 方法
// 给所有 ApplicationListener 广播 ApplicationStartedEvent 事件
listeners.starting();
try {
// 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 创建并准备 Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 打印Banner图
Banner printedBanner = printBanner(environment);
// 创建应用上下文
context = createApplicationContext();
// 创建失败分析器
analyzers = new FailureAnalyzers(context);
// 准备应用上下文
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用上下文
refreshContext(context);
// 触发 ApplicationRunner 与 CommandLineRunner
afterRefresh(context, applicationArguments);
listeners.finished(context, null);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}

忽略掉 StopWatch,正式的启动流程主要分为如下几个步骤:

  1. 配置 awt 的 headless 属性,默认设置为 true,因为SpringBoot应用一般不需要使用awt的重量级组件,如展示窗口或者对话框、接收鼠标与键盘的输入等;
  2. 初始化 SpringApplicationRunListeners,即一组 SpringApplicationRunListener 的集合,通过SPI加载 spring.factories 配置完成初始化,默认配置中只有一个实现类 EventPublishingRunListener,该实现类会通过多播器 SimpleApplicationEventMulticaster 将事件封装为 SpringApplicationEvent 系列事件对象,并将其广播给所有已注册的 ApplicationListener 监听器。
  3. 通过调用 EventPublishingRunListener.starting() 方法给 ApplicationListener 广播 ApplicationStartedEvent 事件
  4. 创建 Environment 并配置 PropertySource 及 profile,调用 EventPublishingRunListener.environmentPrepared() 方法广播 ApplicationEnvironmentPreparedEvent 事件
  5. 创建 ApplicationContext,web应用环境创建 AnnotationConfigEmbeddedWebApplicationContext,其它环境创建 AnnotationConfigApplicationContext
  6. 创建 FailureAnalyzers,实为使用SPI初始化的一组 FailureAnalyzer 对象,主要用于分析系统启动失败的各种原因,并将其转化为可读性更强的文字描述,主要的实现类有 NoSuchBeanDefinitionFailureAnalyzer,NoUniqueBeanDefinitionFailureAnalyzer 等;
  7. 准备 ApplicationContext,调用所有的 ApplicationContextInitializer,调用所有 EventPublishingRunListener.contextPrepared() 方法,但并未广播任何事件,调用 EventPublishingRunListener.contextLoaded() 方法广播 ApplicationPreparedEvent 事件;
  8. 刷新 ApplicationContext,完成Bean的加载及增强,并注册 shutdownHook 用于监听关机消息,以便应用可以优雅停服;
  9. 调用注册好的 ApplicationRunner 及 CommandLineRunner;
  10. 调用 EventPublishingRunListener.finished() 广播 ApplicationFailedEvent 或 ApplicationReadyEvent 事件;

整个流程的文字描述可能很复杂,如果将事件广播的逻辑独立出来,你会发现整个流程就只有文章开头的那四个步骤。

为了更好的对流程进行说明,我们使用一个简单的流程图对整个流程进行拆解总结如下:

SpringBoot启动流程

最后,补充一下 SpringApplicationRunListener 方法与广播事件对象的映射关系:

方法名 事件对象
starting ApplicationStartedEvent
environmentPrepared ApplicationEnvironmentPreparedEvent
contextPrepared 无
contextLoaded ApplicationPreparedEvent
finished ApplicationFailedEvent/ApplicationReadyEvent

参考资料:

qchery wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!