我们先来看下Spring中事件相关的几个成员:
Event):用于区分和定义不同的事件。Multicaster):负责发布事件。Listener):负责监听和处理广播器发出的事件。关系结构图如下:

首先我们自定义一个监听器MyApplicationListener:希望感应应用处理环境变量相关的事件。
@Component
public class MyApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
System.out.println(this.toString() + " received: " + event);
}
}
那么此时我们希望SpringBoot在启动过程中,凡是和ApplicationEnvironmentPreparedEvent相关的事件,我们都将其抛出来打个日志。那么程序启动后,结果如下:

可见并没有我们想要的结果。
我们从SpringBoot项目启动的代码开始走起:
SpringApplication.run(Main8080.class, args);
↓↓↓↓↓↓↓↓↓↓
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
// ...
SpringApplicationRunListeners listeners = this.getRunListeners(args);
// ...
// 环境准备
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
// ...
}
}
而我们希望监听环境准备时候的相关事件,可见,这部分逻辑必定发生在prepareEnvironment()函数中。那在这之前,Spring做了什么操作呢?即getRunListeners()。
我们关注这行代码:
SpringApplicationRunListeners listeners = this.getRunListeners(args);
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class[]{SpringApplication.class, String[].class};
return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
这里的意思就是寻找SpringApplicationRunListener类型的监听器。而EventPublishingRunListener就是他的一个子类。

我们来看下它是如何获得的,代码继续往下走:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
重点关注:SpringFactoriesLoader.loadFactoryNames
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// ..
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// ..
}
// ..
}
我们可以看到,Spring通过类加载器,加载指定的资源,而这个FACTORIES_RESOURCE_LOCATION的值是啥呢?

而这个文件在哪?首先我来说下我的SpringBoot项目引入的包:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
那么我们来看下它的相关资源文件:

SpringBoot容器启动的时候,相关的监听器都会放在SpringApplication.listeners属性中。我们来看下部分文件内容:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
可以发现,SpringApplicationRunListener类型的监听器只有EventPublishingRunListener一个。那么说白了this.getRunListeners(args);这段代码实际上就是加载EventPublishingRunListener监听器的。
那么我来说下EventPublishingRunListener是干啥的吧。它是SpringBoot中唯一一个实现了SpringApplicationRunListener接口的监听器,其主要作用是,根据SpringBoot程序启动过程的不同阶段发布对应的事件,进行对应阶段的事件广播操作。
现在我们知道,EventPublishingRunListener相当于一个枢纽,它下面有一个事件广播器,那么只有注册到这个广播器的监听器,才有可能对相关的事件进行推送。我们来看下代码:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
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();
Iterator var3 = application.getListeners().iterator();
while(var3.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var3.next();
this.initialMulticaster.addApplicationListener(listener);
}
}
}
这里面initialMulticaster就扮演着事件广播器的角色,我们看下deubg:这里Spring就尝试着将下面的监听器都注册到initialMulticaster中。即this.initialMulticaster.addApplicationListener(listener);

可见,这里并没有我们自定义的MyApplicationListener监听器,即没有注册到initialMulticaster广播器中。因此再Spring的环境准备阶段,是不会有事件推送到我们自定义的监听器中的。
那么上图中的这些监听器又从哪里加载的?我们依旧从run函数开始:
SpringApplication.run(Main8080.class, args);
↓↓↓↓↓↓↓
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
↓↓↓↓↓↓↓看SpringApplication构造↓↓↓↓↓↓↓
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// ...
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// ...
}
↓↓↓↓↓↓↓getSpringFactoriesInstances↓↓↓↓↓↓↓
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = this.getClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
↓↓↓↓↓↓↓SpringFactoriesLoader.loadFactoryNames↓↓↓↓↓↓↓
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// ..
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// ..
}
// ..
}
我们来看下它的部分文件内容(和监听器有关):

那么结合上面的listeners截图,我们可以看出这个内容是吻合的。
总结下就是:
SpringApplication.run(Main8080.class,args);这个函数,我们可以分为两个步骤。一个是SpringApplication的构造。一个是run()函数的调用。SpringApplication构造的过程中,会去META-INF/spring.factories目录下寻找ApplicationListener类型的监听器。run()执行过程中。会通过this.getRunListeners(args);去找到一个SpringApplicationRunListener类型的监听器。同样是去META-INF/spring.factories目录下寻找。唯一实现为EventPublishingRunListener。它主要负责在启动过程的不同阶段发布对应的事件。EventPublishingRunListener中有个initialMulticaster广播器,将第二步中获得的所有监听器都注册进来。然后如果有对应的事件,就会推送给他们。MyApplicationListener监听器,并没有在第二步中获取到,因此在容器的环境准备阶段,并不会将相关的事件推送过来。方式一:在启动的时候,直接将我们自定义的监听器加入到广播器中:
@SpringBootApplication
public static void main(String[] args) {
MyApplicationListener myApplicationListener = new MyApplicationListener();
SpringApplication springApplication = new SpringApplicationBuilder(Main8080.class)
.listeners(myApplicationListener).build();
springApplication.run(args);
// SpringApplication.run(Main8080.class,args);
}
debug验证下:这时候就多出来我们自定义的MyApplicationListener 监听器了。

输出结果如下:xxx received: xxx

方式二(推荐):创建一个目录META-INF/spring.factories,在里面添加自定义的监听器:
org.springframework.context.ApplicationListener=\
com.listener.MyApplicationListener
注意改一下自定义监听器的全路径。
首先,本文案例对于事件类型的选择还是比较特殊的。我们选择监听了ApplicationEnvironmentPreparedEvent事件。那么这个事件就发生于SpringBoot的环境准备阶段,也就是:
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
但是请读者们注意,在这个阶段之前,Spring加载到的监听器都是通过META-INF/spring.factories目录下配置来的。那么我们自定义的监听器除了采取上述方式以外,啥时候可以加入到容器里呢?你可以复制一份MyApplicationListener2出来:

我们可以关注下SpringBoot启动的源码,即run()部分:
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
// ..
this.refreshContext(context);
// ..
}
}
↓↓↓↓↓↓↓
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// 1.刷新上下文环境,做一些准备工作,如对系统属性或者环境变量进行验证
prepareRefresh();
// 2.初始化BeanFactory,进行XML读取。此步骤结束后,ApplicationContext就拥有了BeanFactory的功能。
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3.对BeanFactory进行填充,对 @Qualifier 和 @Autowired 注解进行支持
prepareBeanFactory(beanFactory);
try {
// 4.处理子类覆盖方法
postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// 5.激活各种BeanFactory处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 6.注册 [拦截bean的创建过程] 的处理器
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// 7.为上下文初始化Message,国际化处理
initMessageSource();
// 8.初始化应用消息广播器
initApplicationEventMulticaster();
// 9.让子类来初始化其他的bean
onRefresh();
// 10.在已经注册的bean中寻找监听器,并注册到消息广播器中
registerListeners();
// 11.初始化非惰性单例bean
finishBeanFactoryInitialization(beanFactory);
// 12.完成刷新过程
finishRefresh();
}
catch (BeansException ex) {
// ...
// 若失败了,则销毁该bean,并且重置刷新状态
destroyBeans();
cancelRefresh(ex);
throw ex;
}
finally {
resetCommonCaches();
contextRefresh.end();
}
}
}
}
详细的可以看俺的这篇文章Spring源码系列 - ApplicationContext容器的功能扩展。本篇文章只讲和监听器有关的重点。我们看下registerListeners函数:
protected void registerListeners() {
// 1.先把已经加载好的监听器加入到广播器。即`META-INF/spring.factories`下的
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
// 2.在Spring容器中的Bean中,寻找实现了ApplicationListener接口的Bean,即监听器。
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
// 3.推送事件
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
首先对于第一块内容,本文案例中,由于MyApplicationListener加在了我们自定义的spring.factories文件中,因此它能够第一时间被加载。

其次对于第二块内容,本文案例中,MyApplicationListener2实现了ApplicationListener接口,然后通过@Component注解注入到容器中。就是一个很简单的Bean而已。在第二步中注册到广播器中。

最后再总结下吧:
META-INF/spring.factories文件去进行配置。因为通过这种方式配置的监听器,能够更早的注册到广播器中SpringBoot启动的时候,有个refresh()函数,主要是用于初始化和加载所有需要管理的Bean。其中也会将我们自定义的监听器加入到广播器中。META-INF/spring.factories文件的方式去配置了。例如,如果我自定义一个监听器,监听ApplicationReadyEvent事件,即容器已经准备完毕,那么此时我onRefresh()函数肯定是执行完毕的。我们自定义的监听器在这一阶段也已经加入到广播器了,那么相关的事件是能够正常的接收的,证明:
@Component
public class MyApplicationListener2 implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("MyApplicationListener2: " + this.toString() + " received: " + event);
}
}
结果:
