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中的自定義事件相關邏輯也是由它實作,所以該類例外重要,由于篇幅原因,這部分內容下篇博客決議,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/345724.html
標籤:java
上一篇:java之資料型別+運算子
