Spring Boot事件发布与订阅机制
一、引入
Spring boot启动方法SpringApplication#run(String...)
中有很多关键时间节点:
public ConfigurableApplicationContext run(String... args) {
// ...
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 方法内部会调用listeners.environmentPrepared(bootstrapContext, environment);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 方法内部会调用listeners.contextPrepared(context)和listeners.contextLoaded(context);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
// ...
try {
listeners.running(context);
}
// ...
}
可以看出整个启动主线包括starting
,environmentPrepared
,contextPrepared
,contextLoaded
,started
,running
, failed
时间节点,而对这些时间节点的处理都调用了SpringApplicationRunListeners
相应方法。
lass SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
private final ApplicationStartup applicationStartup;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners,
ApplicationStartup applicationStartup) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
this.applicationStartup = applicationStartup;
}
void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
(step) -> {
if (mainApplicationClass != null) {
step.tag("mainApplicationClass", mainApplicationClass.getName());
}
});
}
// environmentPrepared, contextPrepared, contextLoaded, started, running和starting方法
// 会调用doWithListeners方法, 这里省略;
// failed, failedcallFailedListener这里省略, 感兴趣可以参考源码;
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
doWithListeners(stepName, listenerAction, null);
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
Consumer<StartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
// 依次调用listeners中每个SpringApplicationRunListener相应方法
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
二、调用链
SpringApplicationRunListeners
对这些时间节点的处理只是依次调用了listeners
中每个SpringApplicationRunListener
相应处理这些时间节点的方法,并在处理这些时间节点前后调用DefaultApplicationStartup#start
和StartupStep#end
启动和结束相应时间阶段,那listeners
中包含哪些SpringApplicationRunListener
呢?
看下SpringApplicationRunListeners
工厂方法:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
getRunListeners
最终会调用了一个很重要方法SpringFactoriesLoader#loadFactoryNames
获取SpringApplicationRunListener
接口实现类名,那这些类名来自哪里呢?
1. Spring Boot SPI 机制
如果熟悉java SPI
,可以清晰的类比SpringFactoriesLoader
是Spring
对java SPI
的一种私有扩展,该方法执行步骤如下:
- 首先尝试从内存缓存中获取,如获取到就立即返回,没有则继续下面步骤;
- 扫描
classpath
中的META-INF/spring.factories
文件; - 循环遍历读取这些文件中的键值对(
K->List<V>
); - 将读取内容放入内存缓存,下次再调用这个方法时会优先从缓存中获取;
- 上一步返回的是
Map<String, List<String>>
,还需要以全类名type
作为key
,从Map
中获取对应值;
org/springframework/boot/spring-boot/2.4.4/spring-boot-2.4.4-sources.jar Jar包spring.factories
文件中有如下几行:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
说明
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
这行代码运行结果是names
肯定包含org.springframework.boot.context.event.EventPublishingRunListener
这一项。而且其他Jar包的spring.factories
文件中均不包含org.springframework.boot.SpringApplicationRunListener
,所以names
只包含这一个元素。
createSpringFactoriesInstances()
方法,顾名思义是对names集合中所有全类名实例化,其中几行关键代码:
for (String name : names) {
try{
// 根据全类名获取Class
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
// 获取参数类型为new Class<?>[] { SpringApplication.class, String[].class }的构造函数
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 反射创建对象, 传参为{this, args}, this为SpringApplication本身,args为启动函数传参
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
}
}
再看一下EventPublishingRunListener
参数为new Class<?>[] { SpringApplication.class, String[].class }
的构造函数,其实其只有这个构造函数。
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();
// 将SpringApplication中的所有listener添加到initialMulticaster中
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
那SpringApplication
中的listeners
又是什么呢?
2、Spring Boot 事件
到这里才正真出现事件概念,看下SpringApplication
对listeners`的定义:
private List<ApplicationListener<?>> listeners;
listeners
泛型为ApplicationListener
,ApplicationListener
才是正真是事件监听者,ApplicationListener
接口只继承JDK的事件监听者EventListener
,说明Spring时间可以和JDK事件通用:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
// 事件event发生时的处理逻辑
void onApplicationEvent(E event);
static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(Consumer<T> consumer) {
return event -> consumer.accept(event.getPayload());
}
}
泛型E
表示监听的事件类型,需要继承ApplicationEvent
,ApplicationEvent
表示具体事件类型,自定义事件必须继承该接口,由于Spring兼容JDK事件,所以ApplicationEvent
继承JDK事件EventObject
;
SpringApplication
会在构造函数中完成对listeners
的初始化操作:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 初始化listeners
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
可以看出Spring Boot 也是使用SPI机制对listeners
进行初始化的。
3. 调用链路
现在回头看一下,Spring Boot启动过程中每个时间节点(starting
,environmentPrepared
,contextPrepared
,contextLoaded
,started
,running
,failed
)会调用SpringApplicationRunListeners
相应方法,而SpringApplicationRunListeners
会调用内部listeners
中每个SpringApplicationRunListener
相应处理这些时间节点的方法,而SpringApplicationRunListeners
内部的listeners
只包含一个EventPublishingRunListener
,相当于调用EventPublishingRunListener
相应方法;
SpringApplication -> SpringApplicationRunListeners -> EventPublishingRunListener
看下EventPublishingRunListener
对这些时间节点作何处理:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
// 构造方法上文已贴出, 这里省略
// 省略 getOrder()
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
// 广播事件
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
// environmentPrepared, contextPrepared函数类似starting函数, 这里省略
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
// 如果时间监听器实现ApplicationContextAware接口, 则调用setApplicationContext()方法回写SpringApplication
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
// 广播事件
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
// running方法类似started方法, 这里省略
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
// 省略内部静态类LoggingErrorHandler
}
从源码可以看出,EventPublishingRunListener
对所有时间节点都做了一件时间:将时间节点封装成事件,然后通过SimpleApplicationEventMulticaster
或者ConfigurableApplicationContext
广播出去。
时序图如下:
图中ConfigurableApplicationContext
实现ApplicationEventPublisher
接口。
在前文已经分析过,EventPublishingRunListener
构造函数会将所以ApplicationListener
事件监听器设置到SimpleApplicationEventMulticaster
中,这样SimpleApplicationEventMulticaster
在广播事件时,就知道哪些监听器监听了哪些时间节点事件,如果监听了该事件,就调用该监听器的处理逻辑。
所以如果组件对Spring boot启动过程中某个时间感兴趣,只需要实现ApplicationListener
接口,并将实现类加入到META-INF/spring.factories
文件即可,这样就可以实现启动过程和组件自定义逻辑代码解耦合。
org/springframework/boot/spring-boot/2.4.4/spring-boot-2.4.4-sources.jar Jar包的META-INF/spring.factories
文件包含如下几行:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.env.EnvironmentPostProcessorApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
这里添加了几个事件监听器,简单看下EnvironmentPostProcessorApplicationListener
在Spring Boot环境准备好后做的逻辑处理:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
EnvironmentPostProcessorApplicationListener
会在Spring Boot环境准备好后调用所有EnvironmentPostProcessors
后置处理方法,在后置方法中可以做添加属性源等操作。
比如其中一个环境后置处理器RandomValuePropertySourceEnvironmentPostProcessor
后置处理逻辑是,在环境准备好后添加随机数属性源RandomValuePropertySource
,Environment
就可以解析随机数占位符。
@Component
public class FooBar implements EnvironmentAware, CommandLineRunner{
private Environment env;
@Override
public void setEnvironment(Environment environment) {
this.env = environment;
}
@Override
public void run(String... args) throws Exception {
// 随机生成一个整数
System.err.println(env.resolvePlaceholders("${random.int}"));
// 随机生成一个整数,指定上边界,不大于等于1
System.err.println(env.resolvePlaceholders("${random.int(1)}"));
// 随机生成一个整数,使用区间[0,1)
System.err.println(env.getProperty("random.int(0,1)"));
// 随机生成一个整数,使用区间[100,101),前闭后开=>只能是100
System.err.println(env.getProperty("random.long(100,101)"));
// 随机生成一个 uuid
System.err.println(env.resolvePlaceholders("${random.uuid}"));
}
}
SimpleApplicationEventMulticaster
不仅是这里用到,在Spring Boot中的自定义事件相关逻辑也是由它实现,所以该类异常重要。由于篇幅原因,这部分内容下篇博客解析。