上一章中我们分析了 SpringApplication 类实例化的源代码,在此过程中完成了基本配置文件的加载和实例化。当 SpringApplication 对象被创建之后, 通过调用其 run 方法来进行SpringBoot 的启动和运行,至此正式开启了 SpringApplication 的生命周期。
本章介绍的内容同样是 Spring Boot 运行的核心流程之一,我们将会围绕 SpringApplicationRunListeners、ApplicationArguments、ConfigurableEnvironment 以及 应用上下文信息等部分展开讲解。
在分析和学习整个 run 方法的源代码及操作之前,我们先通过图 4-1 所示的流程图来看一下SpringApplication 调用的 run 方法处理的核心操作都包含哪些。然后,后面的章节我们再逐步细化分析每个过程中的源代码实现。
上面的流程图可以看出,SpringApplication 在 run 方法中重 点做了以下操作。
.获取监听器和参数配置。
.打印 Banner 信息。
.创建并初始化容器。
监听器发送通知。
当然,除了核心操作,run 方法运行过程中还涉及启动时长统计、异常报告、启动日志、异常处理等辅助操作。
对照流程图,我们再来整体看一下入口 run 方法的源代码,核心部分的功能已通过注释的形式进行说明。
public ConfigurableApplicationContext run(String... args) {
//创建 stopwatch 对象, 用于统 i 计 run 方法启动时长
StopWatch stopWatch = new StopWatch();
//启动统计
stopwatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> except ionReporters = new Arraylis
t<>();
//配置 headless 属性
configureHeadlessProperty();
//获得 SpringAppl icat ionRunL istener 数组
//该数组封装 FSpringAppl icat ionRunL isteners 对象的 L isteners 中
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动监听,遍历 SpringAppl icat ionRunL istener 数组每个元素,并执行
listeners .starting();
try {创建 Appl icat ionArguments 对象
ApplicationArguments applicationArguments = new DefaultApplicationArgum
ents(
args);
//加载属性配置,包括所有的配置属性(如: appl icat ion. properties 中和外部的属
性配置)
Conf igurableEnvironment environment = prepareEnvironment(listeners,
applicationArg
uments);
configureIgnoreBeanInfo( environment);
//打 Banner
Banner printedBanner = printBanner ( environment);
//创建容器
context = createApplicationContext();
//异常报告器
exceptionReporters = getSpringFactoriesInstances(
Spr ingBootExcept ionReporter .class ,
new Class[] { ConfigurableApplicat ionContext.class }, context);
//准备容器,组件对象之间进行关联
prepareContext(context, environment, listeners, applicationArguments, p
rintedBanner);
// 初始化容器
refreshContext(context);
//初始化操作之后执行,默认实现为空
afterRefresh( context, applicationArguments);
//停止时长统计
stopWatch. stop();
//打印启动日志
if (this. logStartupInfo) {
new StartupInfoLogger(this . mainApplicat ionClass)
.logStarted(getApplicationLog(), stopwatch);
//通知监昕器:容器启动完成
listeners . started( context);
//调用 Appl icat ionRunner 和 CommandL ineRunner 的运行方法。
callRunners (context, applicat ionArguments);
} 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;
}
在整体了解了整个 run 方法运行流程及核心代码后,下面我们针对具体的程进行讲解。
监听器的配置与加载
让我们忽略 Spring Boot 计 时和统计的辅助功能,直接来看 SpringApplicationRunListeners获取和使用 SpringApplicationRunL isteners可以理解为一个 SpringApplicationRunListener的容器,它将 SpringApplicationRunListener 的集合以构造方法传入, 并赋值给其 listeners成员变量,然后提供了针对 listeners 成员变量的各种遍历操作方法,比如,遍历集合并调用对应的 starting、started、 running 等方法。
SpringApplicationRunListeners 的构建很简单,图 4-1 中调用的 getRunListeners 方法也只是调用了它的构造方法。SpringApplication 中 getRunListeners 方法代码如下。
private SpringApplicationRunListeners getRunListeners(String[] args) {
//构造 Class 数组
Class<?>[] types = new Class<?>[] { SpringApplication. class, String[].cla
/调用 SpringAppl icat ionRunL isteners 构造方法
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstan
ces
SpringApplicationRunListener .class, types, this, args));}
SpringApplicationRunListeners 构 造 方 法 的 第 二 个 参 数 便 是 SpringApplicationRunL
istener 的 集 合 , SpringApplication 中 调 用 构 造 方 法 时 该 参 数 是 通 过
getSpringFactoriesInstances 方法获取的,代码如下。
private <T> Collection<T> getSpringF actoriesInstances(Class<T> type,
Class<?>[] parameterTypes, object... args) {
//加 META- TNE/sprina. factori es 中对应监听器的配
并将结果存 F'set 中(去重)
Set<Strine> names= newLinkedHashSet<>(
个命的能直,
SpringF actoriesl oader. loadFactoryNames(type, classloader));
/文悯化监听器
List<T> instances = createSpringFactories Instances (type, parameterTypes,
classLoader, args, nam
排序
Annotat ionAwareOrderComparator .sort(instances);
eturn instances ; }
通过方法名便可得知,getSpringFactoriesInstances 是用来获取 factories 配置文件中的注册类,并进行实例化操作。
关于通过 SpringFactoriesL oader 获取 META-INF/spring.factories 中对应的配置,前面章节已经多次提到,这里不再赘述。
SpringApplicationRunListener 的注册配置位于 spring-boot 项目中的 spring.factories 文件内,Spring Boot 默认仅有- -个监听器进行了注册,关于其功能后面会专门讲到。
# RunListeners
org. springframework. boot.SpringApplicationRunListener=
org. springframework. boot. context.event.EventPublishingRunL istener
我们继续看实例化监听器的方法 createSpringFactoriesInstances 的源代码。
private <T> List<T> createSpringFactoriesInstances(Class<T> type
Class<?>[] parameterType
s, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names)
Class<?> instanceClass = ClassUtils . forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
//获取有参构造器
Constructor<?> constructor = instanceClass . getDeclaredConstructor(par
ameterTypes);
T instance = (T) BeanUtils. instant iateClass(constructor, args);
instances . add(instance);
return instances;
}
在上面的代码中,实例化监听器时需要有一-个默认的构造方法, 且构造方法的参数为Class<?>[ ] parameterTypes。我们向上追踪该参数的来源,会发现该参数的值为 Class 数组 , 数 组 的 内 容 依 次 为 SpringApplication.class 和 String[ ].class 。 也 就 是 说 ,SpringApplicationRunL istener 的实现类必须有默认的构造方法,且构造方法的参数必须依次为 SpringApplication 和 String[ ]类型。
SpringApplicationRunListener 源码解析
接口 SpringApplicationRunListener 是 SpringApplication 的 run 方法监听器。上节提到了SpringApplicationRunListener 通过 SpringFactoriesL oader 加载,并且必须声明一个公共构造函数,该函数接收 SpringApplication 实例和 String[ ]的参数,而且每次运行都会创建一个新的实例。
SpringApplicationRunListener 提供了-系列的方法,用户可以通过回调这些方法,在启动各个流程时加入指定的逻辑处理。下面我们对照源代码和注释来了解一下该接口都定义了哪些待实现的方法及功能。
public interface SpringApplicationRunL istener {
// 当 run 方法第- 次被执行时,会被立即调用,可用于非常早期的初始化工作 default void
starting(){};
//当 environment 准备完成, 在 Appl icationContext 创建之前, 该方法被调用 default void
environmentPrepared(Conf igurableEnvironment environment) {};
//当 ApplicationContext 构建完成, 资源还未被加载时, 该方法被调用 default void
contextPrepared(ConfigurableApplicationContext context) {}
// 当 Appl icat ionContext 加 载 完 成 , 未 被 刷 新 之 前 , 该 方 法 被 调 用 default void
contextLoaded(ConfigurableApplicationContext context) {};
//当 ApplicationContext 刷新并启动之后, CommandL ineRunner 和 Appl icat ionRunner 未
被调用之前, 该方法被调用
default void started(Conf igurableApplicationContext context) {};
//当所有准备工作就绪,run 方法执行完成之前, 该方法被调用
default void running(ConfigurableApplicationContext context) {};
//当应用程序出现错误时,该方法被调用
default void failed(ConfigurableApplicationContext context, Throwable exception) {};
}
我们通过源代码可以看出,SpringApplicationRunListener 为 run 方法提供了各个运行阶段的监听事件处理功能。需要注意的是,该版本中的接口方法定义使用了 JAVA8 的新特性,方法已采用 default 声明并实现空方法体,表示这个方法的默认实现,子类可以直接调用该方法,也可以选择重写或者不重写。
图 4-2 展示了在整个 run 方法的生命周期中 SpringApplicationRunListener 的所有方法所处的位置,该图可以帮助我们更好地学习 run 方法的运行流程。在前面 run 方法的代码中已经看到相关监听方法被调用,后续的源代码中也将涉及对应方法的调用,我们可参考此图以便理解和加深记忆。
实现类 EventPublishingRunListener
EventPublishingRunL istener 是 SpringBoot 中针对 SpringApplicationRunListener 接口的唯内建实现EventPublishingRunL istener使用内置的SimpleApplicationEventMulticaster来广播在上下文刷新之前触发的事件。
默认情况下,Spring Boot在初始化过程中触发的事件也是交由EventPublishingRunListener来代理实现的。EventPublishingRunListener 的构造方法如下。
public EventPublishingRunListener(SpringApplication application, Stringa
rgs) {
this.application = application;
this.args = args;
//创建 SimpleAppl icat ionEventMulticaster/播器
this . initialMulticaster = new SimpleApplicationEventMulticaster();
//遍历 Appl icat ionL istener 并关联 S impleAppl icat ionEventMulticaster
for (ApplicationListener<?> listener : application. getListeners()) {
this. initialMulticaster . addApplicationListener(listener);
}
通过源代码可以看出,该类的构造方法符合 SpringApplicationRunListener 所需的构造方法参数要求,该方法依次传递了 SpringApplication 和 String[ ]类型。在构造方法中初始化了该类的 3 个成员变量。
-application :类 型为 SpringApplication ,是当前运行的 SpringApplication 实例。
-args:启动程序时的命令参数。
-initialMulticaster:类 型为 SimpleApplicationEventMulticaster,事件广播器。
Spring Boot 完成基本的初始化之后,会遍历 SpringApplication 的所有 ApplicationListener实 例 , 并 将 它 们 与 SimpleApplicationEventMulticaster 进 行 关 联 , 方 便SimpleApplicationEvent-Multicaster 后续将事件传递给所有的监听器。
EventPublishingRunListener 针对不同的事件提供了不同的处理方法,但它们的处理流程基本相同。
下面我们根据图 4-3 所示的流程图梳理一下 整个事件的流程。
.程序启动到某个步骤后,调用 EventPublishingRunListener 的某个方法。
EventPublishingRunListener 的具体方法将 application 参数和 args 参数封装到对应的事件中。这里的事件均为 SpringApplicationEvent 的实现类。
.通过成员变量 initialMulticaster 的 multicastEvent 方法对事件进行广播,或通过该方法的ConfigurableApplicationContext 参数的 publishEvent 方法来对事件进行发布。
.对应的 ApplicationListener 被触发,执行相应的业务逻辑。
下面是 starting 方法的源代码,可对照上述流程进行理解。该方法其他功能类似,代码不再展示。
public void starting() {
this. initialMulticaster . multicastEvent(
new ApplicationStartingEvent (this. application, this.args));}
在上述源代码中你是否发现-个问题,某些方法是通过 initialMulticaster 的 multicastEvent进行事件的广播,某些方法是通过 context 参数的 publishEvent 方法来进行发布的。这是为什么呢?在解决这个疑问之前,我们先看一个比较特殊的方法 contextL oaded 的源代码。
public void contextLoaded( ConfigurableApplicationContext context) {
//遍历 application 中的所有监听器实现类
for (ApplicationL istener<?> listener : this . application. getL isteners()) {
//如果为 Appl icationContextAware,则将上:下文信息设置到该监听器内
if (listener instanceof Applicat ionContextAware) {
((ApplicationContextAware) listener). setApplicationContext( context);//将 application 中的监听器实现类全部添加到上下文中
context . addApplicationL istener(listener);
// / "播事件 Appl icationPreparedEvent
this. initialMulticaster . multicastEvent (
new ApplicationPreparedEvent(this.application, this.args, context));
}
contextLoaded 方法在发布事件之前做了两件事:第一,遍历 application 的所有监听器实现类,如果该实现类还实现了 ApplicationContextAware 接口,则将上下文信息设置到该监听器内;第二,将 application 中的监听器实现类全部添加到上下文中。最后一步才是调用事件广播。
也正是这个方法形成了不同事件广播形式的分水岭,在此方法之前执行的事件广播都是通过multicastEvent 来进行的,而该方法之后的方法则均采用 publishEvent 来执行。这是因为只有到了 contextL oaded 方法之后,上下文才算初始化完成,才可通过它的 publishEvent 方法来进行事件的发布。
自定义 SpringApplicationRunListener
上面我们一起学习了 SpringApplicationRunListener 的基本功能及实现类的源代码,现在我们自定义-个 SpringApplicationRunListener 的实现类。通过在该实现类中回调方法来处理自己的业务逻辑。
自定义实现类比较简单,可像通常实现一个接口一样,先创建类 MyApplicationRunListener,实现接口 SpringApplicationRunListener 及其方法。然后在对应的方法内实现自己的业务逻辑,以下示例代码中只简单打印方法名称。与普通接口实现唯一不同的是,这里需要指定一-个参数依次为 SpringApplication 和 String[ ]的构造方法,不然在使用时会直接报错。
public class MyApplicationRunListener implements SpringApplicationRunListen
er {
public MyApplicationRunListener ( SpringApplication application, String[]
args){
System. out . println("MyApplicationRunListener constructed function");
@Override
public void starting() {
System. out . println("starting...");
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System. out. println(" environmentPrepared...");
//在此省略掉其他方法的实现 }
当定义好实现类之后,像注册其他监听器一样, 程序在 spring.factories 中进行注册配置。如果项目中没有 spring.factories 文件,也可在 resources 目录下先创建 META-INF 目录,然后在该目录下创建文件 sprig.factories。
spring.factories 中配置格式如下。
# Run Listeners
org. springframework. boot . SpringApplicationRunListener=
com. secbro2. learn. listener . MyApplicationRunListener
启动 Spring Boot 项目,你会发现在不同阶段打印出不同的日志,这说明该实现类的方法已经被调用。