本文的关注点是Spring的事件播发机制在SpringBoot中是如何发扬光大的。
1. 概述
在笔者之前的博客 SpringBoot源码研究之Start 中,已经基本完整地阐述了SpringBoot应用地整个启动逻辑,SpringBoot的事件机制正是通过将整个启动逻辑划分为不同的阶段,然后在关键阶段点触发相应的预定义事件,进而回调相应的注册事件处理函数来实现对外扩展。
SpringBoot中对于事件的处理相关的package为 org.springframework.boot.context.event
。依然是非常经典的所有相关的类都被封装在一个专门的package中。
下面就让我们挑选出一些关键性的内容进行分析。
2. 事件监听—— EventPublishingRunListener
关于SpringBoot的启动逻辑,我们在之前的文章中已经分析过一次,因此本次我们将只关注事件相关的。
在以下源码中我们看到一共会触发了五类事件:ApplicationStartingEvent
,ApplicationEnvironmentPreparedEvent
,ApplicationPreparedEvent
,ApplicationReadyEvent
,ApplicationFailedEvent
(最后这两个互斥,只会触发其中的一个)。这正好对应上图中的 event 子包下的相关类。
// SpringApplication.java
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// 从META-INF/spring.factories文件中检索出SpringApplicationRunListener实现类,默认情况下也就是 EventPublishingRunListener 了。
// org.springframework.boot.SpringApplicationRunListener= org.springframework.boot.context.event.EventPublishingRunListener
// 并将这些检索出来的实现类封装为 SpringApplicationRunListeners,对外提供统一的调用接口, 非常好的思路
SpringApplicationRunListeners listeners = getRunListeners(args);
// 触发ApplicationStartingEvent事件
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 触发 ApplicationEnvironmentPreparedEvent 事件
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
// 触发 ApplicationPreparedEvent 事件
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
// 触发 ApplicationReadyEvent / ApplicationFailedEvent 中的某一个事件
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);
}
}
触发顺序正好和笔者之前的博客SpringBoot源码分析之LOG 中所测试的相一致。
另外观察以上源码我们会发现,SpringApplicationRunListeners
的构造并不是简单地直接使用new
,所以我们需要注意下SpringBoot是如何构建整个SpringApplicationRunListeners
实例的,知晓这些扩展点有助于我们在碰到业务需求时候更加从容。
// 我们可以很清晰地看到SpringBoot依然是从META-INF/spring.factories文件中检索出的各个SpringApplicationRunListener实现类,来组装成SpringApplicationRunListeners实例。
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
// 抽取配置文件中的 SpringApplicationRunListener实现类
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 这里说明了会考虑Ordered接口实现, 或Ordered相关的注解@Order and @Priority。
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
以下就是SpringBoot配置SpringApplicationRunListener
,使其可以介入SpringBoot生命周期。
最后让我们来看看EventPublishingRunListener
本身。通过观察此类的实现我们发现,其对于事件的触发只是简单地迭代调用了所有注册的ApplicationListener
。而ApplicationListener
的注册方式和上面的SpringApplicationRunListener
一致,观察以下截图(其中最后的两个类正是笔者上一篇博客SpringBoot源码分析之LOG 的关键点):
注意,EventPublishingRunListener
使用的是自身构建的ApplicationEventMulticaster
实例,而非Spring容器自带的,所以播发的事件不会被Spring容器感知。
以上注册了这么多ApplicationListener
,分别完成了SpringBoot在启动和运行过程中各种扩展操作,例如ConfigFileApplicationListener
中就有各种各样的配置参数,例如profile配置文件相关的spring.profiles.active
等等。
// args 为 命令行参数
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
// 这里的application.getListeners(), 获取的是从META-INF/spring.factories文件中检索出出来的 ApplicationListener 实现类
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
3. 事件对象 —— SpringApplicationEvent
与Listener相照应的当然就是事件对象了。虽然Spring 提供了初始版本的事件对象——即继承自EventObject
的 ApplicationEvent
。但SpringBoot依然选择了构建了一个抽象层 SpringApplicationEvent
,用以保证其自身的稳定性。
上图中我们可以看到,SprnigBoot提供了五种不同类型的事件对象,SpringBoot使用它们来将大部分配置逻辑外置化,而不是硬编码到主体逻辑中,这种微核+扩展的设计方法有着无比强大的生命力。当然使用者也可以通过实现并注册自定义的ApplicationListener
来介入到SpringBoot生命周期中。
// 在src/main/resources下新建META-INF/spring.factories文件
// 在上述文件中填入 org.springframework.context.ApplicationListener=com.xx.yy.MyListener
// 启动应用
public final class MyListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(event.getClass().getName());
}
}
// ------ 输出顺序
1. org.springframework.boot.context.event.ApplicationStartedEvent
2. org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent
3. 输出banner
4. org.springframework.boot.context.event.ApplicationPreparedEvent
5. org.springframework.context.event.ContextRefreshedEvent
6. org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent
7. org.springframework.boot.context.event.ApplicationReadyEvent
4. 总结
- SpringBoot增加了
SpringApplicationRunListener
接口来用于监听SpringBoot应用启动过程中的一些生命周期事件,并做一些处理。 - SpringBoot增加了
SpringApplicationEvent
事件基类和五种实现类,用于针对SpringBoot启动过程中的不同阶段的扩展。并提供了META-INF/spring.factories
的配置入口。SpringBoot启动的很多配置信息就是使用这种方式完成的。