在该系列上一篇文章中,我们分析了应用根据classpath
存在的Web
环境的特征类的存在性,判断出当前Web
环境是WebApplicationType.REACTIVE
。这一篇文章,我们主要来分析应用程序上下文ApplicationContext
的创建,初始化和准备过程。这一过程,主要体现在类SpringApplication
实例成员方法ConfigurableApplicationContext run(String... args)
:
// 类 SpringApplication 代码片段
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 包装通过命令行传入的名命令行参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 结合命令行参数 准备环境对象,该环境对象将会被设置到应用上下文对象 ApplicationContext 上 ,
// 环境对象通常包含如下信息 :
// 1. profile
// 2. system properties
// 3. system environment
// 4. commandline arguments
// 5. spring 配置文件
// 6. 一个随机值属性源 random
// 对于当前 WebFlux 应用来讲,这里实现类会使用 StandardReactiveWebEnvironment
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
// 创建应用上下文对象 ApplicationContext
// 实现类会采用 : AnnotationConfigReactiveWebServerApplicationContext
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 准备应用上下文对象 ApplicationContext
// 1. 关联环境信息对象到应用上下文对象
// 2. 对象创建后置处理 : 设置容器的类型转换服务
// 3. 初始化应用上下文对象:调用各个 ApplicationContextInitializer
// 4. 广播事件 : ApplicationContextInitializedEvent
// 5. 将应用程序参数作为一个 bean 注册到容器 : springApplicationArguments
// 6. 将应用程序入口类作为 bean 注册到容器 (load)
// 7. 上下文加载完成生命周期事件回调,为各个实现了 接口 ApplicationContextAware 的
// ApplicationListener 设置应用上下文对象属性, 并广播事件 : ApplicationPreparedEvent
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新应用上下文对象 ApplicationContext
// 主要是调用应用上下文对象 ApplicationContext 自身的 refresh 方法
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 应用程序上下文对象 ApplicationContext 已经准备就绪,
// 现在调用各种开发人员或者框架其他部分定义的
// 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;
}
SpringApplication#run
的逻辑其实是应用启动的整个过程,其中,能体现应用启动的关键步骤主要是 :
prepareEnvironment
createApplicationContext
prepareContext
refreshContext
我们将逐一分析 。这篇文章中,我们主要分析 :
prepareEnvironment
createApplicationContext
prepareContext
这三个步骤覆盖了应用上下文对象创建前的准备工作:创建环境信息对象,创建应用上下文对象本身,以及准备应用上下文。当应用上下文准备好之后,必须要调用第四个步骤refreshContext
刷新应用上下文,才能算是应用完全启动。不过对于refreshContext
主要是调用应用上下文对象的refresh
方法,我们将其放到下一篇重点分析。
1. prepareEnvironment
// SpringApplication 代码片段
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// 创建环境信息对象 environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 将应用程序参数关联到环境信息对象 environment
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 发布应用程序事件 : 环境信息对象准备好了 ,
// 同步调用各个事件监听器
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
以上代码针对各种Web
环境通用,所以这里看不出针对Reactive Web
环境有什么特殊的地方。我们需要深一步观察:
// SpringApplication 代码片段
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();
}
}
这段代码是用来创建环境信息对象的,从其逻辑不难看出,它根据属性this.webApplicationType
决定环境信息对象使用哪个实现类,从上一篇的分析我们已经可以知道,this.webApplicationType
是REACTIVE
,所以,这里的环境信息对象实现类会使用StandardReactiveWebEnvironment
。
2. createApplicationContext
准备好环境对象之后,SpringApplication
要创建应用上下文对象了,该动作实现在方法createApplicationContext
中:
// SpringApplication 代码片段
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 根据 this.webApplicationType 确定应用上下文实现类
switch (this.webApplicationType) {
case SERVLET:
// DEFAULT_SERVLET_WEB_CONTEXT_CLASS 常量值为 :
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
// 对应 Spring MVC Servlet Web 环境
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
// DEFAULT_REACTIVE_WEB_CONTEXT_CLASS 常量值为 :
// org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
// 对应 Spring WebFlux Reactive Web 环境
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
// DEFAULT_CONTEXT_CLASS 常量值为 :
// org.springframework.context.annotation.AnnotationConfigApplicationContext
// 不对应任何 Web 环境
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",
ex);
}
}
// 确定应用上下文实现类之后,实例化应用上下文对象
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
跟getOrCreateEnvironment
方法类似,createApplicationContext
也根据this.webApplicationType
决定应用上下文对象使用哪个实现类。在本系列文章所用的例子中,显然这里是org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
。
3.prepareContext
// SpringApplication 代码片段
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments,
Banner printedBanner) {
// 1. 关联环境信息对象到应用上下文对象
context.setEnvironment(environment);
// 2. 对象创建后置处理 : 设置容器的类型转换服务
postProcessApplicationContext(context);
// 3. 初始化应用上下文对象:调用各个 ApplicationContextInitializer
applyInitializers(context);
// 4. 广播事件 : ApplicationContextInitializedEvent
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 5. 将应用程序参数作为一个 bean 注册到容器 : springApplicationArguments
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 6. 将应用程序入口类作为 bean 注册到容器 (load)
load(context, sources.toArray(new Object[0]));
// 7. 上下文加载完成生命周期事件回调,为各个实现了 接口 ApplicationContextAware 的
// ApplicationListener 设置应用上下文对象属性, 并广播事件 : ApplicationPreparedEvent
listeners.contextLoaded(context);
}
从方法prepareContext
实现来看,它针对不同实现类的ApplicationContext
的处理流程并无不同,换句话讲,以上流程无论是针对Servlet Web
环境的AnnotationConfigServletWebServerApplicationContext
,还是WebFlux Reactive Web
环境的AnnotationConfigReactiveWebServerApplicationContext
,所做的事情都一样。
总结
本文分析了应用上下文对象创建到准备的以下步骤 :
prepareEnvironment
createApplicationContext
prepareContext
这三个步骤覆盖了应用上下文对象创建前的准备工作:创建环境信息对象,创建应用上下文对象本身,以及准备应用上下文。这个过程中,跟WebFlux Reactive Web
紧密相关的要点如下 :
this.webApplicationType
是REACTIVE
,这一点在上面文章分析中已经明确;- 环境信息对象的实现类根据
this.webApplicationType
的值选用StandardReactiveWebEnvironment
; - 应用上下文对象的实现类根据
this.webApplicationType
的值选用AnnotationConfigReactiveWebServerApplicationContext
;