文章目录
- 6.2 启动SpringApplication
- 6.2.1 前置准备
- 6.2.1.1 计时器对象的使用
- 6.2.1.2 awt的设置
- 6.2.1.3 对比SpringBoot 2.0.x-2.2.x
- 6.2.1.4 对比SpringBoot 2.4.x
- 6.2.2 获取SpringApplicationRunListeners
- 6.2.2.1 EventPublishingRunListener
- 6.2.2.2 与其他版本的对比
- 6.2.3 准备运行时环境
- 6.2.3.1 创建运行时环境
- 6.2.3.2 配置运行时环境
- 6.2.3.3 Environment与SpringApplication的绑定
在SpringApplication的构造方法执行完毕后,SpringApplication对象的创建工作就完成了,下一步会执行SpringApplication的
run
方法,从而真正启动SpringBoot应用。
6.2 启动SpringApplication
整个run方法可以分为13步,本节梳理一下前面3步:
代码清单1:SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
//6.2.1 前置准备
//6.2.1.1 计时器对象的使用
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
//6.2.1.2 awt的设置
configureHeadlessProperty();
//6.2.2 获取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//6.2.3 准备运行时环境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//...
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
} catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
} catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
6.2.1 前置准备
6.2.1.1 计时器对象的使用
代码清单2:SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
//6.2.1 前置准备
//6.2.1.1 计时器对象的使用
StopWatch stopWatch = new StopWatch();
stopWatch.start();
//...
try {
// ...
stopWatch.stop();
// ...
} catch (Throwable ex) {
// ...
}
// ...
}
由 代码清单2 可知,run方法的第一步前置准备中首先会初始化一个StopWatch对象。
Simple stop watch, allowing for timing of a number of tasks, exposing total running time and running time for each named task.
简单的秒表,允许计时多个任务,暴露总运行时间和每个命名任务的运行时间。
This class is normally used to verify performance during proof-of-concept work and in development, rather than as part of production applications.
这个类常用于在概念验证和开发过程中验证性能,而不是作为生产应用的一部分。
由javadoc可知,StopWatch是一个用于验证性能的秒表。换言之,StopWatch用来监控SpringBoot应用启动的时间。在run方法刚启动时,执行stopWatch.start()
开始计时,当后面的try块即将执行完毕,执行stopWatch.stop()
停止计时,由此计算出SpringBoot应用启动的大体耗时。
6.2.1.2 awt的设置
代码清单3:SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
//6.2.1 前置准备
// ...
//6.2.1.2 awt的设置
configureHeadlessProperty();
// ...
}
由 代码清单3 可知,前置准备中会接着执行configureHeadlessProperty
方法。
代码清单4:SpringApplication.java
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private void configureHeadlessProperty() {
System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}
由 代码清单4 可知,该方法从System获取了一个配置属性 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS ,然后又把获取到的值重新设置回System。
这一步的逻辑看起来很诡异,但从JDK的System类源码可以看出这样做的目的。
代码清单5:System.java
public static String getProperty(String key) {
// ...
// 从Properties中取值,如果没有取到则返回null
return props.getProperty(key);
}
public static String getProperty(String key, String def) {
// ...
// 从Properties中取值,如果没有取到则返回默认值
return props.getProperty(key, def);
}
public static String setProperty(String key, String value) {
// ...
// 设置key和value
return (String) props.setProperty(key, value);
}
由 代码清单4、5 可知,configureHeadlessProperty
方法调用的是返回默认值的那一个getProperty
方法,这样做是为了给配置属性 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 设置一个默认值。
而常量 SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 的值是“java.awt.headless”,是指显示器缺失,因此configureHeadlessProperty
方法的真正作用是:设置该配置属性后,在启动SpringBoot应用时即使没有检测到显示器也允许其继续启动。(部署应用的服务器大多没有显示器,也可以运行服务)。
6.2.1.3 对比SpringBoot 2.0.x-2.2.x
SpringBoot 2.0到2.2版本中(本文源码版本是2.3.11),在前置准备阶段还有一行代码:
代码清单6:SpringApplication.java
// SpringBoot 2.0.x-2.2.x
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// 创建异常分析器集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// ...
}
由 代码清单6 可知,异常分析器集合的泛型是SpringBootExceptionReporter类,它是一个用于支持SpringApplication启动错误报告的接口,本身也是利用SPI机制加载的。
SpringBootExceptionReporter默认只有一个实现类:FailureAnalyzers。关于FailureAnalyzers的设计,在 SpringBoot源码解读与原理分析(十四)SpringApplication的总体设计 4.1.1 启动失败的错误报告 中已详细梳理过。
6.2.1.4 对比SpringBoot 2.4.x
由于SpringBoot 2.4.0版本后引入了新的API:BootstrapRegistry,在前置准备阶段会创建DefaultBootstrapContext,并用所有的BootstrapRegistryInitializer。
代码清单7:SpringApplication.java
// SpringBoot 2.4.x
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 创建DefaultBootstrapContext
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
//...
}
private DefaultBootstrapContext createBootstrapContext() {
DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext));
return bootstrapContext;
}
6.2.2 获取SpringApplicationRunListeners
代码清单8:SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
//...
//6.2.2 获取SpringApplicationRunListeners
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
//...
}
由 代码清单8 可知,run方法的第二步是获取监听器。在 SpringBoot源码解读与原理分析(十四)SpringApplication的总体设计 4.1.5 监听与回调 中已详细梳理过SpringApplicationRunListeners的设计。
这里关注一下默认加载的监听器实现。借助IDEA的Debug,可以发现默认加载的监听器实现是EventPublishingRunListener。
6.2.2.1 EventPublishingRunListener
代码清单9:EventPublishingRunListener.java
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
// 初始化事件广播器
this.initialMulticaster = new SimpleApplicationEventMulticaster();
// 存储所有监听器
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
//...
}
从类名上理解,EventPublishingRunListener具备事件发布的能力,而发布事件必定要配合事件广播器来实现。由 代码清单9 可知,EventPublishingRunListener构造完成之后,其内部已经初始化了一个事件广播器SimpleApplicationEventMulticaster,并整合了现阶段可以获取到的所有ApplicationListener。
EventPublishingRunListener实现了SpringApplicationRunListener接口的各个方法,在每个方法中都有一个特殊的事件被广播。
代码清单10:EventPublishingRunListener.java
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
this.initialMulticaster
.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
//...
}
6.2.2.2 与其他版本的对比
SpringBoot 1.x并没有把所有的切入时机都考虑到,只有starting
、environmentPrepared
、contextPrepared
、contextLoaded
,另外还有一个finished
。
SpringBoot 2.x在原来基础上扩展了started
、running
、failed
,删掉了finished
。
SpringBoot 2.4.x中,引入了BootstrapContext。由于这个组件会在ApplicationContext初始化之前起作用,而SpringApplicationRunListener的前两个事件触发时BootstrapContext处于有效状态,因此前两个事件把BootstrapContext作为触发入参,以便在扩展事件监听逻辑时有更多API可以获取,进而有机会做更多的事情。
代码清单11:SpringApplicationRunListener.java
default void starting(ConfigurableBootstrapContext bootstrapContext) {
starting();
}
default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
environmentPrepared(environment);
}
6.2.3 准备运行时环境
代码清单12:SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
//6.2.1 前置准备
//6.2.1.1 计时器对象的使用
//6.2.1.2 awt的设置
//6.2.2 获取SpringApplicationRunListeners
try {
//6.2.3 准备运行时环境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//...
}
获取监听器之后,第三步是准备运行时环境,对应的是Environment抽象。Environment的设计,在 SpringBoot源码解读与原理分析(十)Environment 已详细梳理过。
代码清单13:SpringApplication.java
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 创建、配置运行时环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 回调SpringApplicationRunListener的environmentPrepared方法
listeners.environmentPrepared(environment);
// 环境与应用绑定
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
由 代码清单13 可知,准备运行时环境主要分为3步:创建、配置运行时环境;回调SpringApplicationRunListener的environmentPrepared方法;环境与应用绑定。
6.2.3.1 创建运行时环境
在 SpringBoot源码解读与原理分析(十)Environment 3.4.2 Environment的结构与设计 中,借助IDEA生成Environment接口的上下级继承和派生关系。
其中有3个Environment的落地实现类,分别对应普通环境的StandardEnvironment、支持Servlet环境的StandardServletEnvironment和支持Reactive环境的StandardReactiveWebEnvironment。
代码清单14:SpringApplication.java
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new StandardServletEnvironment();
case REACTIVE:
return new StandardReactiveWebEnvironment();
default:
return new StandardEnvironment();
}
}
由 代码清单14 可知,在创建运行时环境,就是根据之前推断好的Web类型来决定使用何种Environment的落地实现。
6.2.3.2 配置运行时环境
代码清单15:SpringApplication.java
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
// 获取全局共享的ApplicationConversionService实例
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
// 将命令行参数封装为PropertySource
configurePropertySources(environment, args);
// 编程式添加激活的profile
configureProfiles(environment, args);
}
由 代码清单15 可知,配置运行时环境包括配置ConversionService、将命令行参数封装为PropertySource、编程式添加激活的profile。
6.2.3.3 Environment与SpringApplication的绑定
代码清单16:SpringApplication.java
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
} catch (Exception ex) {
throw new IllegalStateException("Cannot bind to SpringApplication", ex);
}
}
由 代码清单16 可知,Binder类将Environment中的一些以 spring.main 开头的配置属性映射到SpringApplication中,而SpringApplication的部分属性恰好就是以 spring.main 开头的,例如spring.main.lazy-initialization=true
。
因此,bindToSpringApplication
方法的作用就是将application.properties中的属性赋值到SpringApplication对象中。
······
本节完,更多内容请查阅分类专栏:SpringBoot源码解读与原理分析。
下节继续梳理run
方法的剩余步骤:IOC容器的创建和初始化、IOC容器刷新后的回调。