【spring】 spring-boot 启动过程

从源码分析 spring-boot 应用的启动过程

spring factory 机制

在 spring-core 中有一套机制,可以在 META-INF/spring.factories 文件中定义 interface-class 的键值对,然后在需要用到 interface 的地方通过 SpringFactoriesLoader 将所有 class 实例化。

spring-boot 启动过程

spring-boot 应用使用 SpringApplication 类开始启动

1
SpringApplication.run(App.class, args);

静态的 run 方法会 new 一个 SpringApplication 实例,再调用 run 成员方法,在 SpringApplication 的构造方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 根据 classpath 有没有特定的类决定 web 应用是 REACTIVE (反应式) 还是 SERVLET
this.webApplicationType = WebApplicationType.deduceFromClasspath();

// 通过 SpringFactoriesLoader,从 spring.factories 读取 ApplicationContextInitializer 类的配置
// 并实例化后保存到 initializers 成员
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

// 通过 SpringFactoriesLoader,从 spring.factories 读取 ApplicationListener 类的配置
// 并实例化后保存到 listeners 成员
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

// 通过 RuntimeException().getStackTrace() 找到 main 方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}

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
public ConfigurableApplicationContext run(String... args) {
// 计时器,计算启动用的时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();

ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

// 设置 java.awt.headless 系统变量
configureHeadlessProperty();

// 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 下的 SpringApplicationRunListener 并实例化
// 然后来用初始化 SpringApplicationRunListeners
// SpringApplicationRunListeners 来用管理 SpringApplicationRunListener 列表
SpringApplicationRunListeners listeners = getRunListeners(args);

listeners.starting();

try {
// 封装命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

// 准备 Environment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

// 处理 spring.beaninfo.ignore 系统参数
configureIgnoreBeanInfo(environment);

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

// 新建 application context 对象
context = createApplicationContext();

// 从 META-INF/spring.factories 读出并实例化 SpringBootExceptionReporter 类型
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);

// 准备 application context
prepareContext(context, environment, listeners, applicationArguments, printedBanner);

// refresh application context,同一般的 application context 的 refresh 过程一样
refreshContext(context);

// 没用的
afterRefresh(context, applicationArguments);

// 停止计时并打印启动信息
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}

listeners.started(context);

// 调用 ApplicationRunner 和 CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}

try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

getRunListeners

getRunListeners 通过 SpringFactoriesLoader 加载到了 EventPublishingRunListener

EventPublishingRunListener 包含一个 SimpleApplicationEventMulticaster 用来做事件处理, EventPublishingRunListener 提供不同的方法用来发布不同的事件

方法 事件
starting ApplicationStartingEvent
environmentPrepared ApplicationEnvironmentPreparedEvent
contextPrepared ApplicationContextInitializedEvent
contextLoaded ApplicationPreparedEvent
started ApplicationStartedEvent
running ApplicationReadyEvent
failed ApplicationFailedEvent

这些事件由在 spring.factories 中读取的 ApplicationListener 来响应

prepareEnvironment

准备环境,根据不同的 webApplicationType (SpringApplication 构造方法中设值) 生成不同的 Environment 实例,然后读取和设定配置

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
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {

// 创建 Environment 实例, 可能是 StandardServletEnvironment, StandardReactiveWebEnvironment 或 StandardEnvironment 中的一个
ConfigurableEnvironment environment = getOrCreateEnvironment();

// 设置 ConversionService
// 设置 CommandLinePropertySource 和 profile
configureEnvironment(environment, applicationArguments.getSourceArgs());

// 加一个 ConfigurationPropertySourcesPropertySource 到 environment
ConfigurationPropertySources.attach(environment);

// 发布 ApplicationEnvironmentPreparedEvent 事件
// 由 ConfigFileApplicationListener 在这里加载配置文件到 PropertySource 并加入到 environment
// 由 LoggingApplicationListener 设定日志配置信息
listeners.environmentPrepared(environment);

// 将 SpringApplication 所需要的属性绑定, 都是 spring.main 开头的属性
bindToSpringApplication(environment);

// 如果需要的话,转换 environment 的实际类型
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}

// 再做一次 attach。将可能的,第一次 attach 之后加入的数据源再处理一次
ConfigurationPropertySources.attach(environment);
return environment;
}

prepareContext

准备 application context, 在 application context 做 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
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {

context.setEnvironment(environment);
postProcessApplicationContext(context);

// 调用 ApplicationContextInitializer
// 在 SpringApplication 的构造方法中, 会实例化在 META-INF/spring.factories 配置的 ApplicationContextInitializer
// 这里会执行这些 ApplicationContextInitializer 的 initialize 方法
applyInitializers(context);

// 发布事件
listeners.contextPrepared(context);

if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}

// 将参数注册到 beanFactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);

// 将 banner 注册到 beanFactory
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}

if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverridin(this.allowBeanDefinitionOverriding);
}

// 从 sources 加载 bean 配置
// sources 是在创建 SpringApplication 实例时设定的,它可以是一组类,类路径,或资源文件
// load 方法从中获取 bean 配置信息
// 如果 sources 是资源,就用 xml 的方式读取其中的配置
// 如果 sources 是类,就用注解的方式读取其中的配置
// 如果是字符串的话,先尝当成类名来解析,如果不成功就当成资源路径来解析
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));

// 发布事件
listeners.contextLoaded(context);
}

注解

spring boot 应用的一个重要的注解是 @SpringBootApplication,它是一个合成的注解,由 3 个注解组成:

  • @SpringBootConfiguration,相当于 @Configuration,所以可以在有 @SpringBootApplication 的类里通过 @Bean 来配置;
  • @EnableAutoConfiguration,开启 auto configuration 功能;
  • @ComponentScan,扫描类路径,找通过 @Component 等注解配置的 bean