文章目录
- 通过源码明晰的几个问题
- Spring 中的事件
- Springboot 是怎么做到事件监听的
- 另外两种注册的Listener
- 源码解析
- 加载listener
- SpringApplicationRunListener
- EventPublishingRunListener
- SimpleApplicationEventMulticaster
- 判断 listener 是否可以接收事件
- Java 泛型获取
- 整体流程回顾
- Springboot事件分发流程
- SimpleApplicationEventMulticaster 事件分发流程
- 问题解答
- 1. Springboot 注册事件监听器有几种方式,分别是什么?
- 2. 什么情况下注册的事件监听会失效(接收不到Springboot事件)?
- 3. Springboot 利用的哪一个类做的事件分发操作?
- 4. Spring 是如何利用泛型做到的事件分发?
- 5. 怎么自定义 listener 达到监听多个类型事件?
- 6. 除了Spring或Springboot自己提供的事件,怎么自定义事件?
- 最后
通过源码明晰的几个问题
通过解读 Springboot 的事件分发源码,了解一下几个问题:
- Springboot 注册事件监听器有几种方式,分别是什么?
- 什么情况下注册的事件监听会失效(接收不到Springboot事件)?
- Springboot 利用的哪一个类做的事件分发操作?
- Spring 是如何利用泛型做到的事件分发?
- 怎么自定义 listener 监听多个类型事件?
- 除了Spring或Springboot自己提供的事件,怎么自定义事件?
这些问题将会在看源码的过程中一一清晰,在文末会总结这几个问题的答案。
源码分析中为避免代码过多,会忽略无关紧要的代码。
Spring 中的事件
在 Springboot 中要监听 Spring 中的事件,需要实现 ApplicationListener 这个接口,这个接口不是 Springboot 提供的,而是 Spring 提供的。
在代码中使用步骤如下:
@Component
public class TestBean implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
// 处理 ApplicationStartedEvent 事件
}
}
这里可以看到,通过实现 ApplicationListener 监听到了 ApplicationStartedEvent 事件,这个事件将会在 Springboot 应用启动成功后接收到。而 ApplicationStartedEvent 这个事件并不是 Spring 提供的,而是Springboot提供的,这个很重要,能理解这个区别后面自定义事件就好理解了。
Springboot 是怎么做到事件监听的
另外两种注册的Listener
- SPI(spring.factories)方式
除了上面通过 bean 方式注册事件监听器,还有两种方式注册事件监听器。一种是我们通过SPI定义在 spring.factories 文件中的 listener。
具体原理可与看我之前的文章。
Springboot 的 spring.factories 文件中定义了下面几种listener, Springboot 就是通过这种 listener 完成的各种Spring的各种加载配置。
# 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
- 通过程序注册
另一种方式可能听过的比较少,在Springboot程序启动之前是可以设置事件监听的。
@SpringBootApplication
public class SpringDemoApplication {
ConfigurableApplicationContext run = new SpringApplicationBuilder(SpringDemoApplication.class)
// 使用SpringApplicationBuilder启动Springboot应用,调用 listeners 函数手动设置监听器
.listeners(new CustomListener()).run(args);
// 或者 创建 SpringApplication 对象,调用 addListeners 函数手动设置监听器
SpringApplication application = new SpringApplicationBuilder(SpringDemoApplication.class).build(args);
application.addListeners(new CustomListener());
ConfigurableApplicationContext run = application.run();
}
源码解析
那么 Springboot 是怎么做到这种方式的事件分发呢?具体来看源码。
加载listener
在Springboot的启动类的构造函数中,通过SPI方式加载了配置在SPI配置文件中的 listener
SpringApplicaton
private List<ApplicationListener<?>> listeners;
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//从配置文件中加载事件列表
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//...
}
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
// 程序手动添加listener最终都会调用这个方法
public void addListeners(ApplicationListener<?>... listeners) {
this.listeners.addAll(Arrays.asList(listeners));
}
需要注意的是,这里收集的只是后面提到的两种listener,通过bean注册的并没有被收集到,为什么,因为Bean需要bean容器启动才能收集到,Bean容器还没有启动和准备好,怎么可能收集到呢?这里只是收集到了listener,怎么做的分发呢。
SpringApplicationRunListener
在Springboot中提供了接口,SpringApplicationRunListener
- 它是一个接口
- 该接口提供了整个 SpringBoot 生命周期的回调函数
- 实现这个接口,必须提供一个 (SpringApplication, String[]) 的构造函数, 其中SpringApplication 就是 SpringBoot 的启动类, String[] 提供的是命令行参数。
Springboot 就是通过它做的事件分发,同样这个接口也是通过SPI进行注册的。
SpringAppliction
public ConfigurableApplicationContext run(String... args) {
// 获取 SpringApplicationRunListeners 这个 listeners 是一个包装类,里面包装了所有从配置文件中读取到的 SpringApplicationRunListener 集合
// 通过这个 listeners 调用 SpringApplicationRunListener 的对应方法,同时进行一些日志记录、运行时间记录等操作
SpringApplicationRunListeners listeners = getRunListeners(args);
// 调用 listeners 的 staring 方法
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 在prepareEnvironment 中调用了 listeners.environmentPrepared()
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 在 prepareContext 中调用了 listeners.contextPrepared() 和 contextLoaded()
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 启动成功后 调用 started()
listeners.started(context, timeTakenToStartup);
} catch (Throwable ex) {
// 执行失败调用 listeners 的 failed 方法
handleRunFailure(context, ex, listeners);
}
try {
// springboot 应用准备完毕后调用 ready()
listeners.ready(context, timeTakenToReady);
}
return context;
}
private SpringApplicationRunListeners getRunListeners(String[] args) {
// 这里说明 SpringApplicationRunListener 必须提供 SpringApplication, String[] 的构造函数
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
// 从配置文件中读取所有的 SpringApplicationRunListener 封装进 SpringApplicationRunListeners 包装类中
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
回调函数 | 说明 | 对应的listener |
---|---|---|
starting(context) | 在Springboot程序启动时被调用 | ApplicationStartingEvent |
environmentPrepared(context,environment) | 在Springboot环境准备好后被调用,也就是说各种配置读取完成后被调用 | ApplicationEnvironmentPreparedEvent |
contextPrepared(context) | 在Springboot上下文准备好后被调用,就是各种Bean还没有加载完成 | ApplicationContextInitializedEvent |
contextLoaded(context) | 在Springboot上下文加载完成后被调用,这时候Bean都已经被加载完成了 | ApplicationPreparedEvent |
started(context, duration) | 在Springboot启动后CommandLineRunner和ApplicationRunner被调用前被调用 | ApplicationStartedEvent |
ready(context, duration) | 在CommandLineRunner 和 ApplicationRunner 调用后被调用 | ApplicationReadyEvent |
failed(context, throwable) | 启动失败后被调用 | ApplicationFailedEvent |
从上面就可以看到,Springboot 启动中的各种事件就是通过读取配置文件中的 SpringApplicationRunListener 完成的,具体是哪一个类呢?
EventPublishingRunListener
在spring.factories 中能看到这么一个唯一的实现类:EventPublishingRunListener
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
从名字就能看出来,它就是做事件分发的
EventPublishingRunListener
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.initialMulticaster = new SimpleApplicationEventMulticaster();
// 把application中收集到的listener设置给SimpleApplicationEventMulticaster
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public void starting(ConfigurableBootstrapContext bootstrapContext) {
// 分发事件 ApplicationStartingEvent
this.initialMulticaster
.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
// 分发事件 ApplicationEnvironmentPreparedEvent
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
// 分发事件 ApplicationContextInitializedEvent
this.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
// 把application的listener添加的 Context 中、
// 这个事件列表,加上context(bean容器)中的listener就是所有listener了
context.addApplicationListener(listener);
}
// 分发事件 ApplicationPreparedEvent
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context, Duration timeTaken) {
// 这里不同了,在 context 已经加载完毕的时候就可以使用 context 来分发事件了
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
}
@Override
public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
// 在 context 已经加载完毕的时候就可以使用 context 来分发事件了
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
// 出现失败可能在Springboot的任意启动过程中,所以要判断 context 是否已经加载完毕,加载完毕使用 context 分发事件
if (context != null && context.isActive()) {
context.publishEvent(event);
} else {
// 省略部分代码... 未加载完毕使用 默认的方式分发事件
this.initialMulticaster.multicastEvent(event);
}
}
}
从代码中可以看到几点:
- 通过调用 SimpleApplicationEventMulticaster 的 multicastEvent 方法完成的事件分发
- 在 context 容器加载完毕之后 (contextLoaded) 之后,使用 context.publishEvent 进行事件分发了,因为这时候所有的事件都已经被收集到 context 中了 (包括SPI注册的,Bean 注册的)
- 在启动失败时,会判断 context 是否加载完成决定使用哪种方式进行处理。那么也就说明,我们在代码中通过Bean方式注册的 ApplicationFailedEvent 只会在 Context 加载完成后起作用。
那么这里就可以看出来,什么情况下配置的listener会不起作用呢?有两个条件:
- listener 是通过Bean注册的
- 在Context加载完成之前
因为 Bean 在 contextLoaded 之后才会被加载进入 Springboot 容器中,所以通过这种方式只能注册下面这三种 listener- ApplicationStartedEvent
- ApplicationReadyEvent
- ApplicationFailedEvent
SimpleApplicationEventMulticaster
无论是 SPI 的事件分发,还是 Bean 方式的事件分发,都是依赖 SimpleApplicationEventMulticaster 来完成的。通过调用函数 publishEvent 进行事件分发。
- 该类是 Spring 提供的,而不是Springboot 提供的。
- 从名字可以看出来,它是一个事件广播器,把事件广播给所有注册该事件的 listener
- 该类提供了 setExecutor 方法,可以让事件分发通过线程池多线程方式执行,但是Springboot的事件分发中并没有使用。
SimpleApplicationEventMulticaster
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
// 查找到符合该类型的 listener
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// 如果线程池存在,使用线程池执行 listener
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
// 如果线程池不存在,直接调用 listener
invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
} catch (Throwable err) {
// 如果有异常处理器,调用异常处理器逻辑(这部分在 SpringApplicationRunListener failed() 方法中有用到,用于打印日志)
errorHandler.handleError(err);
}
} else {
doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
// 打印日志或 向上抛出异常,省略该部分代码...
}
}
}
上面的整个事件分发的处理逻辑比较简单,关键是,怎么找到该事件对应的 listeners 呢?
下面的流程源码的流程大致是:
- 根据 event 的类型和event对应的source类型进行缓存
- 从缓存总查找对应的listener,如果缓存中不存在,从全部listener中过滤出来需要的listener,放入缓存中
- 这里的listener就分为了两种,通过spi和程序指定的listener因为不会被修改,为一类,直接放入缓存的listener列表中,非单例的bean会因scope的不同每次获取不同的实例,所以通过bean名称进行缓存,获取时通过beanFactory 进行获取。
SimpleApplicationEventMulticaster
protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
// Spring 中的 Event 都会绑定一个 source, 也就是说 source + eventType 就可以唯一确定一类事件
// 所以事件分发的时候,也就是要找到符合这一条件的事件类型的listener
Class<?> sourceType = (source != null ? source.getClass() : null);
// 这里使用了缓存来缓存事件对象
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// 新旧 retriever 的查询逻辑,这里写这么复杂的原因应该是怕多线程情况下两端代码同时走到这一段
CachedListenerRetriever newRetriever = null;
CachedListenerRetriever existingRetriever = this.retrieverCache.get(cacheKey);
if (existingRetriever == null) {
// 这里判断 event 和 source 是否和 beanClassLoader 在同一个 classLoader 下
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
newRetriever = new CachedListenerRetriever();
// 这里应该就是检查一下防止其他线程已经设置了
existingRetriever = this.retrieverCache.putIfAbsent(cacheKey, newRetriever);
// 如果已经被其他线程设置了,那么用以前线程的缓存对象
if (existingRetriever != null) {
newRetriever = null; // no need to populate it in retrieveApplicationListeners
}
}
}
if (existingRetriever != null) {
// 能进来这里说明有两种情况
// 1. 根据cacheKey找到了对应的事件列表
// 2. 有其他线程在设置 cacheKey 之前设置了
Collection<ApplicationListener<?>> result = existingRetriever.getApplicationListeners();
if (result != null) {
return result;
}
// result为空,说明其他线程有可能还没有完成事件的过滤填充,所以进行下面的流程
}
// 根据 retriever 过滤 listener
return retrieveApplicationListeners(eventType, sourceType, newRetriever);
}
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, @Nullable Class<?> sourceType, @Nullable CachedListenerRetriever retriever) {
// listener 分为两种类型,一种就是通过 SPI 注册的 listener,在 Application 启动的时候就加载出来的
// 一种是 Bean, 在容器准备好之后可以通过 beanName 从 beanFactory 中拿到
// 这里有两个 listener 列表的原因是:
// allListener 中包括了 beanListener 和 spiListener
// 而 filteredListeners 只有 spiListener
List<ApplicationListener<?>> allListeners = new ArrayList<>();
Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);
// beanListener 列表
Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);
// 所有的 listener 列表, defaultRetriever 缓存了所有的 listener
Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans;
synchronized (this.defaultRetriever) {
listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);
}
// 扫描符合要求的 spiListener
for (ApplicationListener<?> listener : listeners) {
// 判断是否符合
if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
filteredListeners.add(listener);
}
allListeners.add(listener);
}
}
// 扫描所有的 beanListener
if (!listenerBeans.isEmpty()) {
ConfigurableBeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : listenerBeans) {
try {
// 判断该 beanListener 是否符合
if (supportsEvent(beanFactory, listenerBeanName, eventType)) {
ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
// 避免重复
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
// 如果是单例的,添加到 spiListener 中,如果不是,添加到 filteredListenerBeans 中
// 这里是一个优化的点,为什么呢?
// Spring 中默认的Bean是单例的,所以对象被容器创建出来之后对象引用就不会改变了,所以这里把它加到 filteredListeners 中
// 而对于非单例的bean,不同的环境中可能对象会变化,所以这里把他添加到 beans 中,每次获取的时候通过 beanFactory 获取,而不进行缓存
if (beanFactory.isSingleton(listenerBeanName)) {
filteredListeners.add(listener);
} else {
filteredListenerBeans.add(listenerBeanName);
}
}
allListeners.add(listener);
}
}
else {
// 不支持的bean,从listener 中移除
Object listener = beanFactory.getSingleton(listenerBeanName);
if (retriever != null) {
filteredListeners.remove(listener);
}
allListeners.remove(listener);
}
} catch (NoSuchBeanDefinitionException ex) {
}
}
}
AnnotationAwareOrderComparator.sort(allListeners);
if (retriever != null) {
// 如果不存在 beanListener,也就是说所有的listener都是 spi方式注册的或者是单例的
//(这里其实用 allListener 或者用 filteredListeners效果是一样的)
if (filteredListenerBeans.isEmpty()) {
retriever.applicationListeners = new LinkedHashSet<>(allListeners);
retriever.applicationListenerBeans = filteredListenerBeans;
}
else {
retriever.applicationListeners = filteredListeners;
retriever.applicationListenerBeans = filteredListenerBeans;
}
}
// 如果 retriever 为空,返回所有的 listener
return allListeners;
}
SimpleApplicationEventMulticaster$CachedListenerRetriever
private class CachedListenerRetriever {
// 对象引用不变的listener
@Nullable
public volatile Set<ApplicationListener<?>> applicationListeners;
// 非单例beanlistener(对象引用可能会变)
@Nullable
public volatile Set<String> applicationListenerBeans;
@Nullable
public Collection<ApplicationListener<?>> getApplicationListeners() {
Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;
Set<String> applicationListenerBeans = this.applicationListenerBeans;
if (applicationListeners == null || applicationListenerBeans == null) {
// Not fully populated yet
return null;
}
// 两个合起来才是最终结果
List<ApplicationListener<?>> allListeners = new ArrayList<>(
applicationListeners.size() + applicationListenerBeans.size());
allListeners.addAll(applicationListeners);
if (!applicationListenerBeans.isEmpty()) {
BeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : applicationListenerBeans) {
try {
allListeners.add(beanFactory.getBean(listenerBeanName, ApplicationListener.class));
}
catch (NoSuchBeanDefinitionException ex) {
}
}
}
//...
return allListeners;
}
}
判断 listener 是否可以接收事件
具体是怎么判断 listner 是否支持 listener ?就需要看下面的代码了。
/**
* 推断 listener 是否符合要求
*/
protected boolean supportsEvent(
ApplicationListener<?> listener, ResolvableType eventType, @Nullable Class<?> sourceType) {
GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}
从这里可以看到,主要是通过 GenericApplicationListener, 的两个方法判断是否支持 eventType 和 sourceType的。
- GenericApplicationListener 继承了 SmartApplicationListener
只要实现了这个接口中的这两个方法就能决定 listener 是否支持那些事件了。
但是,我们程序里面写的 listener 其实并没有 实现这两个接口,那是怎么判断的呢?从代码来看,不属于 GenericApplicationListener 的,利用适配器模式包装成了 GenericApplicationListener。也就是 new GenericApplicationListenerAdapter(listener);
public class GenericApplicationListenerAdapter implements GenericApplicationListener {
public GenericApplicationListenerAdapter(ApplicationListener<?> delegate) {
this.delegate = (ApplicationListener<ApplicationEvent>) delegate;
// 获取
this.declaredEventType = resolveDeclaredEventType(this.delegate);
}
@Nullable
private static ResolvableType resolveDeclaredEventType(ApplicationListener<ApplicationEvent> listener) {
// 获取事件类型,如果无法获取,判断是否是代理类,从代理类获取
ResolvableType declaredEventType = resolveDeclaredEventType(listener.getClass());
if (declaredEventType == null || declaredEventType.isAssignableFrom(ApplicationEvent.class)) {
Class<?> targetClass = AopUtils.getTargetClass(listener);
if (targetClass != listener.getClass()) {
declaredEventType = resolveDeclaredEventType(targetClass);
}
}
return declaredEventType;
}
@Nullable
static ResolvableType resolveDeclaredEventType(Class<?> listenerType) {
// 又是缓存
ResolvableType eventType = eventTypeCache.get(listenerType);
if (eventType == null) {
// 获取类的泛型
eventType = ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric();
// 设置缓存
eventTypeCache.put(listenerType, eventType);
}
return (eventType != ResolvableType.NONE ? eventType : null);
}
public boolean supportsEventType(ResolvableType eventType) {
if (this.delegate instanceof GenericApplicationListener) {
return ((GenericApplicationListener) this.delegate).supportsEventType(eventType);
}
else if (this.delegate instanceof SmartApplicationListener) {
Class<? extends ApplicationEvent> eventClass = (Class<? extends ApplicationEvent>) eventType.resolve();
return (eventClass != null && ((SmartApplicationListener) this.delegate).supportsEventType(eventClass));
}
// 上面两行代码都是一样的,没什么可说的 直接调用的扩展方法 supportsEventType 进行判断
// 下面这一行代码是根据泛型进行判断的
else {
return (this.declaredEventType == null || this.declaredEventType.isAssignableFrom(eventType));
}
}
}
上面可以看到,通过 ResolvableType.forClass(listenerType).as(ApplicationListener.class).getGeneric(); 获取到类的泛型。
最后再判断 eventType 是否属于 listener的泛型类型 declaredEventType, 这样基于能拿到具体进行匹配了。
作为扩展,看一下 ResolvableType 是怎么拿到泛型的。
ResolvableType
public ResolvableType[] getGenerics() {
// 缓存
ResolvableType[] generics = this.generics;
if (generics == null) {
if (this.type instanceof Class) {
Type[] typeParams = ((Class<?>) this.type).getTypeParameters();
generics = new ResolvableType[typeParams.length];
for (int i = 0; i < generics.length; i++) {
generics[i] = ResolvableType.forType(typeParams[i], this);
}
}
else if (this.type instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
generics = new ResolvableType[actualTypeArguments.length];
for (int i = 0; i < actualTypeArguments.length; i++) {
generics[i] = forType(actualTypeArguments[i], this.variableResolver);
}
}
else {
generics = resolveType().getGenerics();
}
this.generics = generics;
}
return generics;
}
Java 泛型获取
- class.getTypeParameters 获取直接定义在接口或类上的泛型
interface A<T, E, V> {}
// A getTypeParameters [T, E, V]
System.out.println("A getTypeParameters " + Arrays.toString(A.class.getTypeParameters()));
- type.getActualTypeArguments 获取类实际上的泛型,包括继承、实现的父类中的泛型
interface A<T, E, V> {}
interface B<T, V> {}
static class E implements B<Integer, Double>, A<ApplicationContext, Double, Integer> {}
// E:interface B getActualTypeArguments [class java.lang.Integer, class java.lang.Double]
// E:interface A getActualTypeArguments [interface org.springframework.context.ApplicationContext, class java.lang.Double, class java.lang.Integer]
testGetActualTypeArguments(E.class);
private static void testGetActualTypeArguments(Class<?> clz) {
final Type[] types = clz.getGenericInterfaces();
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType typeP = ((ParameterizedType) type);
System.out.println(clz.getSimpleName() + ":" + typeP.getRawType()
+ " getActualTypeArguments " + Arrays.toString(typeP.getActualTypeArguments()));
} else {
System.out.println(clz.getSimpleName() + " is not a parameterized type");
}
}
}
整体流程回顾
Springboot事件分发流程
- SpringBoot 启动时通过加载 SPI(ApplicationListener) 加载到配置文件中的监听事件
- 加载 SPI(SpringApplicationRunListener),加载 EventPublishRunListener, 通过 SpringApplicationRunListener 在Springboot不同的生命周期中调用对应的 listener
- EventPublishRunListener 通过 SimpleApplicationEventMulticaster 进行事件的分发
- 程序启动时,将SPI和程序中通过Application添加的listener赋值给 SimpleApplicationEventMulticaster,通过它进行事件分发
- 容器准备完成后,容器会注册一个 SimpleApplicationEventMulticaster,把程序启动时的listener传递给context,context传递给SimpleApplicationEventMulticaster。
- 此时,容器中注册的这个SimpleApplicationEventMulticaster包含了所有的listener,此时,就可以通过context进行事件的分发了。
SimpleApplicationEventMulticaster 事件分发流程
- 根据eventType(事件类) + sourceType(source类)检查缓存
- 缓存中存在已经过滤的事件列表,直接返回
- 缓存中不存在过滤的事件列表进行过滤,缓存
- 根据 GenericApplicationListener 的两个方法判断listener是否支持该事件
- 如果listener没有实现GenericApplicationListener接口,则根据泛型的类型判断是否支持该listener
问题解答
至此,整个事件分发的流程也就分析结束了,那么上面的几个问题也就有答案了
1. Springboot 注册事件监听器有几种方式,分别是什么?
有三种方式
- 通过SPI方式,在 spring.factories 中定义 ApplicationListener 进行注册
- 通过创建 SpringApplication 的时候手动添加
- 通过注册Bean实现ApplicationListener方式添加
2. 什么情况下注册的事件监听会失效(接收不到Springboot事件)?
通过Bean方式注册的事件,并且属于容器加载完成前的时间都接收不到,因为通过Bean方式注册的事件监听只能监听到容器加载后的事件。
3. Springboot 利用的哪一个类做的事件分发操作?
Springboot 利用的 EventPublishRunListener 完成的 Springboot 声明周期的事件分发,底层利用的是 SimpleApplicationEventMulticaster
4. Spring 是如何利用泛型做到的事件分发?
Spring 利用 ResolvableType 类获取类的泛型参数,根据该泛型参数判断接收到的事件是否符合该泛型判断该 listener 是否可以处理该事件。
5. 怎么自定义 listener 达到监听多个类型事件?
从源码中可以看到,如果我们制定了具体的事件类型,就只能监听这种类型的事件,比如
public class TestBean implements ApplicationListener<ApplicationStartedEvent> {}
这种方式就只能监听到 ApplicationStartedEvent 这种事件。
要监听多个事件类型有两种方式。
- 泛型中如果写 ApplicationEvent,那么就能监听到所有继承了这个类的事件了。
- 实现 SmartApplicationListener 接口,通过重写 supportsEventType(Class<? extends ApplicationEvent> event) 自定义接收的时间类型逻辑。
6. 除了Spring或Springboot自己提供的事件,怎么自定义事件?
其实通过上面的代码,已经看出来,Springboot 也只是利用了 Spring 的事件机制完成的事件分发,Springboot 利用它完成了Springboot 声明周期事件的分发,但是,实际上 Spring 并没有规定只能接收到 Springboot 规定的这些个事件。
仿照Springboot的发布事件方式,我们完全可以定义自己的事件。
比如我们自定义的事件名称为 CustomEvent, 继承ApplicationEvent,如下:
public class CustomEvent extends ApplicationEvent {
private final String msg;
public CustomEvent(Object source, String msg) {
super(source);
this.msg = msg;
}
public String getMsg() {
return msg;
}
}
那么,bean可以实现 ApplicationListener接口来监听这个事件
@Component
public class TestCustomEventComponent implements ApplicationListener<CustomEvent> {
@Override
public void onApplicationEvent(CustomEvent event) {
System.out.println("我收到自定义的时间啦:" + event.getMsg());
}
}
在需要通信的地方,发送这个事件
final ApplicationEventMulticaster eventMulticaster = context.getBean(ApplicationEventMulticaster.class);
eventMulticaster.multicastEvent(new CustomEvent("", "我是一个自定义的事件"));
这样就能接收到事件了。
那么这么做有什么好处呢?
这里举一个例子,假如有一个 service ,用户执行完成后需要调用短信服务发送短信,需要记录日志。正常情况下可能会这样写。
@Autowire
private SmsService smsService;
@Autowire
private LogService logService;
public void handle() {
// do some thing
smsService.sendSms(...);
logService.recordLog(...);
}
这时候就可以看到, 这个 service 强依赖与另外两个服务。假如将来服务做拆分,改动量就比较大了。
如果用事件分发怎么做呢,可以自定义一个Event,就比如 CommonEvent, 短信服务和日志服务都监听这个 Event
那么上面代码就变成了:
@Autowire
private ApplicationEventMulticaster eventMulticaster;
public void handle() {
// do some thing
eventMulticaster.multicastEvent(new CommonEvent(...))
}
这样 Sercvice 之间代码逻辑就解耦了,怎么样是不是感觉有那么一点点眼熟?没错,项目之间通过消息队列进行解耦和这个这不是特别相似吗?
最后
至此Springboot事件分发源码算是梳理完了,梳理过程中才发现一个小小的事件分发居然会有这么多的细节,以前都没有注意到,比如说什么情况下listener会失效,怎么自定义监听的事件类型,怎么自定义事件等等,看源码的过程不仅仅是学习优秀框架的方式,也是对Spring或者Springboot这个框架进一步加深理解的机会。
前些日子试了一下ai上色,还挺好玩的,附一张最爱的云堇图片。