SpringBoot源码分析(三):SpringBoot的事件分发机制

文章目录

    • 通过源码明晰的几个问题
    • 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 的事件分发源码,了解一下几个问题:

  1. Springboot 注册事件监听器有几种方式,分别是什么?
  2. 什么情况下注册的事件监听会失效(接收不到Springboot事件)?
  3. Springboot 利用的哪一个类做的事件分发操作?
  4. Spring 是如何利用泛型做到的事件分发?
  5. 怎么自定义 listener 监听多个类型事件?
  6. 除了Spring或Springboot自己提供的事件,怎么自定义事件?

这些问题将会在看源码的过程中一一清晰,在文末会总结这几个问题的答案。

源码分析中为避免代码过多,会忽略无关紧要的代码。

Spring 中的事件

在 Springboot 中要监听 Spring 中的事件,需要实现 ApplicationListener 这个接口,这个接口不是 Springboot 提供的,而是 Spring 提供的。
在代码中使用步骤如下:

@Component
public class TestBean implements ApplicationListener<ApplicationStartedEvent> {@Overridepublic 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设置给SimpleApplicationEventMulticasterfor (ApplicationListener<?> listener : application.getListeners()) {this.initialMulticaster.addApplicationListener(listener);}}@Overridepublic void starting(ConfigurableBootstrapContext bootstrapContext) {// 分发事件 ApplicationStartingEventthis.initialMulticaster.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));}@Overridepublic void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {// 分发事件 ApplicationEnvironmentPreparedEventthis.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) {// 分发事件 ApplicationContextInitializedEventthis.initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));}@Overridepublic 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);}// 分发事件 ApplicationPreparedEventthis.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));}@Overridepublic void started(ConfigurableApplicationContext context, Duration timeTaken) {// 这里不同了,在 context 已经加载完毕的时候就可以使用 context 来分发事件了context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));}@Overridepublic void ready(ConfigurableApplicationContext context, Duration timeTaken) {// 在 context 已经加载完毕的时候就可以使用 context 来分发事件了context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));}@Overridepublic 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);}}
}

从代码中可以看到几点:

  1. 通过调用 SimpleApplicationEventMulticaster 的 multicastEvent 方法完成的事件分发
  2. 在 context 容器加载完毕之后 (contextLoaded) 之后,使用 context.publishEvent 进行事件分发了,因为这时候所有的事件都已经被收集到 context 中了 (包括SPI注册的,Bean 注册的)
  3. 在启动失败时,会判断 context 是否加载完成决定使用哪种方式进行处理。那么也就说明,我们在代码中通过Bean方式注册的 ApplicationFailedEvent 只会在 Context 加载完成后起作用。

那么这里就可以看出来,什么情况下配置的listener会不起作用呢?有两个条件:

  1. listener 是通过Bean注册的
  2. 在Context加载完成之前
    因为 Bean 在 contextLoaded 之后才会被加载进入 Springboot 容器中,所以通过这种方式只能注册下面这三种 listener
    1. ApplicationStartedEvent
    2. ApplicationReadyEvent
    3. ApplicationFailedEvent

SimpleApplicationEventMulticaster

无论是 SPI 的事件分发,还是 Bean 方式的事件分发,都是依赖 SimpleApplicationEventMulticaster 来完成的。通过调用函数 publishEvent 进行事件分发。

  • 该类是 Spring 提供的,而不是Springboot 提供的。
  • 从名字可以看出来,它是一个事件广播器,把事件广播给所有注册该事件的 listener
  • 该类提供了 setExecutor 方法,可以让事件分发通过线程池多线程方式执行,但是Springboot的事件分发中并没有使用。

SimpleApplicationEventMulticaster

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {@Overridepublic void multicastEvent(ApplicationEvent event) {multicastEvent(event, resolveDefaultEventType(event));}@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();// 查找到符合该类型的 listenerfor (ApplicationListener<?> listener : getApplicationListeners(event, type)) {// 如果线程池存在,使用线程池执行 listenerif (executor != null) {executor.execute(() -> invokeListener(listener, event));} else {// 如果线程池不存在,直接调用 listenerinvokeListener(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 就可以唯一确定一类事件// 所以事件分发的时候,也就是要找到符合这一条件的事件类型的listenerClass<?> 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 过滤 listenerreturn 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 只有 spiListenerList<ApplicationListener<?>> allListeners = new ArrayList<>();Set<ApplicationListener<?>> filteredListeners = (retriever != null ? new LinkedHashSet<>() : null);// beanListener 列表Set<String> filteredListenerBeans = (retriever != null ? new LinkedHashSet<>() : null);// 所有的 listener 列表, defaultRetriever 缓存了所有的 listenerSet<ApplicationListener<?>> listeners;Set<String> listenerBeans;synchronized (this.defaultRetriever) {listeners = new LinkedHashSet<>(this.defaultRetriever.applicationListeners);listenerBeans = new LinkedHashSet<>(this.defaultRetriever.applicationListenerBeans);}// 扫描符合要求的 spiListenerfor (ApplicationListener<?> listener : listeners) {// 判断是否符合if (supportsEvent(listener, eventType, sourceType)) {if (retriever != null) {filteredListeners.add(listener);}allListeners.add(listener);}}// 扫描所有的 beanListenerif (!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 为空,返回所有的 listenerreturn allListeners;
}

SimpleApplicationEventMulticaster$CachedListenerRetriever

private class CachedListenerRetriever {// 对象引用不变的listener@Nullablepublic volatile Set<ApplicationListener<?>> applicationListeners;// 非单例beanlistener(对象引用可能会变)@Nullablepublic volatile Set<String> applicationListenerBeans;@Nullablepublic Collection<ApplicationListener<?>> getApplicationListeners() {Set<ApplicationListener<?>> applicationListeners = this.applicationListeners;Set<String> applicationListenerBeans = this.applicationListenerBeans;if (applicationListeners == null || applicationListenerBeans == null) {// Not fully populated yetreturn 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);}@Nullableprivate 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;}@Nullablestatic 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 这种事件。

要监听多个事件类型有两种方式。

  1. 泛型中如果写 ApplicationEvent,那么就能监听到所有继承了这个类的事件了。
  2. 实现 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> {@Overridepublic 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 thingsmsService.sendSms(...);logService.recordLog(...);
}

这时候就可以看到, 这个 service 强依赖与另外两个服务。假如将来服务做拆分,改动量就比较大了。

如果用事件分发怎么做呢,可以自定义一个Event,就比如 CommonEvent, 短信服务和日志服务都监听这个 Event
那么上面代码就变成了:

@Autowire
private  ApplicationEventMulticaster eventMulticaster;
public void handle() {// do some thingeventMulticaster.multicastEvent(new CommonEvent(...))
}

这样 Sercvice 之间代码逻辑就解耦了,怎么样是不是感觉有那么一点点眼熟?没错,项目之间通过消息队列进行解耦和这个这不是特别相似吗?

最后

至此Springboot事件分发源码算是梳理完了,梳理过程中才发现一个小小的事件分发居然会有这么多的细节,以前都没有注意到,比如说什么情况下listener会失效,怎么自定义监听的事件类型,怎么自定义事件等等,看源码的过程不仅仅是学习优秀框架的方式,也是对Spring或者Springboot这个框架进一步加深理解的机会。

前些日子试了一下ai上色,还挺好玩的,附一张最爱的云堇图片。
请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/3938.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决 mac 系统报zsh: command not found: npm 问题

文章目录 1、报错zsh: command not found: npm2、解决办法 1、报错zsh: command not found: npm 根据提示&#xff1a;zsh: command not found: npm。说明没有找到 npm 命令&#xff0c;这说明有两种情况&#xff1a; 一是&#xff1a;你根本就没有安装 nodejs 的环境&#xf…

轻量服务器带宽流量和云服务器带宽流量有什么区别?

轻量服务器带宽流量和云服务器带宽流量有什么区别?虽然轻量服务器是轻量化云服务器&#xff0c;但与云服务器的差别还是有一些的&#xff0c;比如这令很多人好奇的轻量服务器带宽和流量和云服务器的区别在哪。下面我们就仔细聊聊关于轻量服务器和云服务器各自的带宽流量差异&a…

JetBrains goland、pycharm、webstorm、phpstorm 对比两文件内容是否一致

对比文件 JetBrains goland、pycharm、webstorm、phpstorm 对比两文件内容是否一致 第一种 打开文件&#xff0c;按住键盘上的CTRL键&#xff0c;然后鼠标右键&#xff0c;点击菜单中的”Compare with Clipboard”&#xff0c;左侧就可以粘贴文件内容对比 第二种 在编辑器窗口中…

【国产虚拟仪器】基于ZYNQ7045+V7 FPGA的多通道数据同步采集设计方案(一)

多通道数据采集设备在当前信息数字化的时代应用广泛&#xff0c;各种被测量的信息 如光线、温度、压力、湿度、位置等&#xff0c;都需要经过多通道信号采集系统的采样和 处理&#xff0c;才能被我们进一步分析利用[37]。在一些对采集速率要求较高的军事、航天、 航空、工业制造…

Python基本操作

前言 啦啦啦&#xff0c;现在开始,打算做一期Python基础教程&#xff0c;欢迎大家来看哦&#xff01; 导读 这期文章真的是Python基础中的基础&#xff0c;相信有一定编程基础的小伙伴们都一定能看懂的… 本文共分为以下几个部分&#xff1a; 数与运算符基本输入输出注释模…

1.网络基础

什么是网络&#xff1f; 信息传递&#xff0c;资源共享 计算机—1946年2月14日—电脑 电流—二进制— 1001—人类语言&#xff08;抽象语言&#xff09;—应用程序—编译—编码—应用层 把人类语言转化为二进制—表示层&#xff08;编码表&#xff09; 网路层——路由器&#x…

汇编的各种指令及使用方法

***************************************************************** 汇编中的符号 1.指令&#xff1a; 能够编译生成一条32位的机器码&#xff0c;且能被CPU识别和执行 2.伪指令&#xff1a;本身不是指令&#xff0c;编译器可以将其替换成若干条等效指令 3.伪操作&#xff1a…

libevent实践04:监听管道第二季

一 本次实例使用函数简介 事件集合初始化&#xff1a; struct event_base *event_init(void); 示例&#xff1a; struct event_base *base event_init(); 单个事件初始化 void event_set(struct event *ev, evutil_socket_t fd, short events,void (*callback)(evutil_s…

leetcode 27.移除元素

⭐️ 题目描述 &#x1f31f; leetcode链接&#xff1a;移除元素 代码&#xff1a; /*思路&#xff1a;双指针问题[3,2,2,3] , val 3src-> [ 3 , 2 , 2 , 3 ]destnums[src] val > srcsrc-> [ 3 , 2 , 2 , 3 ] destnum…

k8s操作命令

系列文章目录 文章目录 系列文章目录一、k8s基础命令1.陈述式资源管理方法&#xff1a;2.基础命令 总结 一、k8s基础命令 1.陈述式资源管理方法&#xff1a; 1.kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口 2.kubectl 是官方的CLI命令行工具…

死锁的发生与避免

文章目录 一&#xff1a;概念二&#xff1a;死锁2.1&#xff1a;互斥条件2.2&#xff1a;请求与保持条件2.3&#xff1a;不可剥夺条件2.4&#xff1a;循环等待条件 三&#xff1a;避免死锁问题的发生四&#xff1a;避免死锁的算法 一&#xff1a;概念 死锁是指在一组进程中的各…

QT 简易视频播放器版本1.1

设计Qt界面实现播放、暂停、停止、下一集、上一集、快进、后退、倍速播放、进度调节&#xff0c;音量调节、视频播放列表等功能 先上演示效果&#xff1a; ui界面设计 videoplayer.h #ifndef VIDEOPLAYER_H #define VIDEOPLAYER_H#pragma execution_character_set("utf-…