当前位置: 代码迷 >> 综合 >> 【Spring基础】从观察者模式到Spirng事件发布与接收(三要素:event listener publisher)
  详细解决方案

【Spring基础】从观察者模式到Spirng事件发布与接收(三要素:event listener publisher)

热度:30   发布时间:2024-02-02 12:26:34.0

文章目录

  • 一、前言
  • 二、源码分析一(发布事件publisher和监听事件listener)
    • 2.1 ApplicationEventPublisher:广播服务的核心能力
    • 2.2 如何广播一条消息
    • 2.3 ApplicationEventPublisherAware
  • 三、源码分析二(接收事件)
  • 四、源码分析三(如何做到只接收指定类型的消息/Spring底层接收指定类型广播)
    • 4.1 接收广播的时候是否存在监听器和消息类型绑定?
    • 4.2 发送广播的时候是否存在监听器和消息类型绑定?
  • 五、开发一个自定义广播
  • 六、开发两个自定义监听器
  • 七、金手指
    • 7.1 金手指1:ApplicationContext发布广播publisherEvent()方法谁调用?
      • 7.1.1 事件的触发事件,从四个事件到自定义事件
      • 7.1.2 事件类的定义:从四个事件到自定义事件
      • 7.1.3 事件监听类的定义:从四个事件监听类到自定义事件监听类
    • 金手指2:事件发布三要素:listener event publisher
    • 金手指3:自己模拟的事件,自己发布
    • 金手指4:彻底总结
      • 事件监听者
        • 事件监听者为什么要使用注解成为bean
        • 事件监听者实现的接口如何确定
        • 事件监听者实现的方法如何确定(key:在Spring找事件监听者listener调用的那个方法)
      • 事件本体
      • 事件发布者
        • 为什么要使用@Service注解注入到SpringIOC容器
        • 为什么要实现XxxAware接口
  • 八、小结

一、前言

二、源码分析一(发布事件publisher和监听事件listener)

金手指:如何广播一条消息:从AbstractApplicationContext类的finishRefresh方法开始
ApplicationEventPublisher类

2.1 ApplicationEventPublisher:广播服务的核心能力

提到spring容器中的广播和监听,就不得不先看看ApplicationEventPublisher这个接口,今天的故事都是围绕此君展开:

@FunctionalInterface
public interface ApplicationEventPublisher {/*** Notify all <strong>matching</strong> listeners registered with this* application of an application event. Events may be framework events* (such as RequestHandledEvent) or application-specific events.* @param event the event to publish* @see org.springframework.web.context.support.RequestHandledEvent*/default void publishEvent(ApplicationEvent event) {publishEvent((Object) event);}/*** Notify all <strong>matching</strong> listeners registered with this* application of an event.* 通知所有匹配的listner*/void publishEvent(Object event);}

金手指:publishEvent()方法通知所有匹配的listner(并不是一个),publishEvent()一定存在一个循环

从上述代码可以看出,发送消息实际上就是在调用ApplicationEventPublisher 接口实现类的publishEvent方法,接下来看看类图,了解在spring容器的设计中ApplicationEventPublisher的定位:

在这里插入图片描述

从上图可见,ApplicationContext继承了ApplicationEventPublisher,那么ApplicationContext的实现类自然也就具备了发送广播的能力,按照这个思路去看一下关键的抽象类AbstractApplicationContext,果然找到了方法实现:

@Override
public void publishEvent(ApplicationEvent event) {publishEvent(event, null);
}

2.2 如何广播一条消息

来看一个具体的广播的例子吧,spring容器初始化结束的时候,会执行AbstractApplicationContext类的finishRefresh方法,里面就会广播一条代表初始化结束的消息,代码如下:

protected void finishRefresh() {// Clear context-level resource caches (such as ASM metadata from scanning).clearResourceCaches();// Initialize lifecycle processor for this context.initLifecycleProcessor();// Propagate refresh to lifecycle processor first.getLifecycleProcessor().onRefresh();// 广播一条消息,类型ContextRefreshedEvent代表spring容器初始化结束publishEvent(new ContextRefreshedEvent(this));// Participate in LiveBeansView MBean, if active.LiveBeansView.registerApplicationContext(this);
}

继续深入publichEvent方法内部,看到的代码如下图所示:
这里写图片描述

上图中的绿色部分的判断不成立,初始化过程中的registerListeners方法就会把成员变量earlyApplicationEvents设置为空,因此广播消息执行的是getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType),在ApplicationContext的实现类中,真正的广播操作交给了成员变量applicationEventMulticaster来完成,没有特殊配置的话,applicationEventMulticaster是SimpleApplicationEventMulticaster类型的实例(详情请看initApplicationEventMulticaster方法);

打开的源码,如下所示,是否有种豁然开朗的感觉?广播的实现是如此简单明了,找到已注册的ApplicationListener,逐个调用invokeListener方法,将ApplicationListener和事件作为入参传进去就完成了广播;

@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {Executor executor = getTaskExecutor();if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {invokeListener(listener, event);}}}

所以,笔者刚才说通知监听该事件的listener,一定会有一个循环,果然。

看到这里,invokeListener方法所做的事情已经很容易猜到了,ApplicationListener是代表监听的接口,只要调用这个接口的方法并且将event作为入参传进去,那么每个监听器就可以按需要自己来处理这条广播消息了,看了代码发现我们的猜测是正确的:

private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {try {//这里就是监听器收到广播时做的事情listener.onApplicationEvent(event);}catch (ClassCastException ex) {String msg = ex.getMessage();if (msg == null || matchesClassCastMessage(msg, event.getClass().getName())) {// Possibly a lambda-defined listener which we could not resolve the generic event type for// -> let's suppress the exception and just log a debug message.Log logger = LogFactory.getLog(getClass());if (logger.isDebugEnabled()) {logger.debug("Non-matching event type for listener: " + listener, ex);}}else {throw ex;}}}

另外还有个细节需要补充,这是在后面做自定义广播的时候考虑到的:如果多线程同时发广播,会不会有线程同步的问题?发送广播的源码一路看下来,发现操作全部在发送的线程中执行的,对各种成员变量的操作也没有出现线程同步问题,唯一有可能出现问题的地方在获取ApplicationListener的时候,这里用到了缓存,有缓存更新的逻辑,而spring已经做好了锁来确保线程同步(双重判断也做了),来看看源码,注意中文注释:

protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {Object source = event.getSource();Class<?> sourceType = (source != null ? source.getClass() : null);ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);//retrieverCache是个ConcurrentHashMapListenerRetriever retriever = this.retrieverCache.get(cacheKey);if (retriever != null) {return retriever.getApplicationListeners();}//如果没有从缓存中取到,就要获取了数据再返回if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {// 这里有锁synchronized (this.retrievalMutex) {retriever = this.retrieverCache.get(cacheKey);//抢到锁之后再做一次判断,因为有可能在前面BLOCK的时候,另一个抢到锁的线程已经设置好了缓存if (retriever != null) {return retriever.getApplicationListeners();}retriever = new ListenerRetriever(true);Collection<ApplicationListener<?>> listeners =retrieveApplicationListeners(eventType, sourceType, retriever);this.retrieverCache.put(cacheKey, retriever);return listeners;}}else {// No ListenerRetriever caching -> no synchronization necessaryreturn retrieveApplicationListeners(eventType, sourceType, null);}}

金手指,源码使用HashMap,不需要线程安全;
源码使用LinkedHashMap,需要保证插入顺序;
源码中使用TreeMap,尚未见过,自定义线程安全
源码中使用ConcurrentHashMap,线程安全,源码中不会使用HashTable,效率太低
//retrieverCache是个ConcurrentHashMap 所以这里要考虑线程安全

看到这里,我们对spring容器内的广播与监听已经有所了解,小结如下:

  1. 广播的核心接口是ApplicationEventPublisher;
  2. 我们见过了如何发起一条广播,监听器如何响应广播;

接下来还不能马上动手实战,我们还有两件事情要想弄清楚:

  1. 我想发自定义的广播消息,如何具备这个发送能力?
  2. 我想接收特定的广播,如何具备这个接收能力?

接下来,继续结合spring源码去找上面两个问题的答案;

2.3 ApplicationEventPublisherAware

Spring发送广播:ApplicationEventPublisherAware

先看消息生产相关的源码,在上一章《spring4.1.8扩展实战之二:Aware接口揭秘》中,我们看到spring容器初始化的时候会对实现了Aware接口的bean做相关的特殊处理,其中就包含ApplicationEventPublisherAware这个与广播发送相关的接口,代码如下(ApplicationContextAwareProcessor.java):

private void invokeAwareInterfaces(Object bean) {if (bean instanceof Aware) {if (bean instanceof EnvironmentAware) {((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());}if (bean instanceof EmbeddedValueResolverAware) {((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);}if (bean instanceof ResourceLoaderAware) {((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);}//如果想在应用内发送广播,需要开发一个实现了ApplicationEventPublisherAware接口的bean,就会在此被注入一个ApplicationEventPublisher对象,借助这个对象就能发送广播了if (bean instanceof ApplicationEventPublisherAware) {((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);}if (bean instanceof MessageSourceAware) {((MessageSourceAware) bean).setMessageSource(this.applicationContext);}if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);}}}

由以上代码可见,我们可以创建一个bean,实现了ApplicationEventPublisherAware接口,那么该bean的setApplicationEventPublisher方法就会被调用,通过该方法可以接收到ApplicationEventPublisher类型的入参,借助这个ApplicationEventPublisher就可以发消息了

三、源码分析二(接收事件)

Spring底层如何接收广播
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);

在spring容器初始化的时候,AbstractApplicationContext类的prepareBeanFactory方法中为所有bean准备了一个后置处理器ApplicationListenerDetector,来看看它的postProcessAfterInitialization方法的代码,也就是bean在实例化之后要做的事情:

在这里插入图片描述
在这里插入图片描述

@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {if (bean instanceof ApplicationListener) {// potentially not detected as a listener by getBeanNamesForType retrievalBoolean flag = this.singletonNames.get(beanName);if (Boolean.TRUE.equals(flag)) {//注册监听器,其实就是保存在成员变量applicationEventMulticaster的成员变量defaultRetriever的集合applicationListeners中this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);}else if (Boolean.FALSE.equals(flag)) {if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {// inner bean with other scope - can't reliably process eventslogger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +"but is not reachable for event multicasting by its containing ApplicationContext " +"because it does not have singleton scope. Only top-level listener beans are allowed " +"to be of non-singleton scope.");}this.singletonNames.remove(beanName);}}return bean;}

如上所示,如果当前bean实现了ApplicationListener接口(金手指:就是代码中的 if (bean instanceof ApplicationListener) { ),就会调用this.applicationContext.addApplicationListener方法将当前bean注册到applicationContext的监听器集合中,后面有广播就直接找到这些监听器,调用每个监听器的onApplicationEvent方法( 金手指:就是上面的doInvokeListener方法中的 listener.onApplicationEvent(event); );

现在把广播与监听的关键代码都看过了,可以开始实战了么?稍等,还有最后一个疑问要弄明白:自定义的消息监听器可以指定消息类型,所有的广播消息中,这个监听器只会收到自己指定的消息类型的广播,spring是如何做到这一点的?

四、源码分析三(如何做到只接收指定类型的消息/Spring底层接收指定类型广播)

Spring底层接收指定类型广播

此问题的答案就在spring源码中,但是看源码之前先自己猜测一下,自定义监听器只接收指定类型的消息,以下两种方案都可以实现:

  1. 注册监听器的时候,将监听器和消息类型绑定;
  2. 广播的时候,按照这条消息的类型去找指定了该类型的监听器,但不可能每条广播都去所有监听器里面找一遍,应该是说广播的时候会触发一次监听器和消息的类型绑定;

4.1 接收广播的时候是否存在监听器和消息类型绑定?

带着上述猜测去spring源码中寻找答案吧,先看注册监听器的代码,按照之前的分析,注册监听发生在后置处理器ApplicationListenerDetector中,看看this.applicationContext.addApplicationListener这一行代码的内部逻辑(金手指:刚才接收的广播的时候我们没有进来看,现在我们进来addApplicationListener方法中看一看):

@Overridepublic void addApplicationListener(ApplicationListener<?> listener) {Assert.notNull(listener, "ApplicationListener must not be null");if (this.applicationEventMulticaster != null) {this.applicationEventMulticaster.addApplicationListener(listener);}else {this.applicationListeners.add(listener);}}

最终跟踪到AbstractApplicationEventMulticaster类的addApplicationListener方法:

@Override
public void addApplicationListener(ApplicationListener<?> listener) {synchronized (this.retrievalMutex) {// Explicitly remove target for a proxy, if registered already,// in order to avoid double invocations of the same listener.Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);if (singletonTarget instanceof ApplicationListener) {//如果因为AOP导致创建了监听类的代理,那么就要在注册列表中清除代理类this.defaultRetriever.applicationListeners.remove(singletonTarget);}//把监听器加入集合defaultRetriever.applicationListeners中,这是个LinkedHashSet实例this.defaultRetriever.applicationListeners.add(listener);this.retrieverCache.clear();}
}

从以上代码我们发现了两个关键点:

  1. AOP是通过代理技术实现的,此时可能通过CGLIB生成了监听类的代理类,此类的实例如果也被注册到监听器集合中,那么广播的时候,按照消息类型就会取出两个监听器实例来了,到时候的效果就是一个消息被两个实例消费了,因此这里要先清理掉代理类;
  2. 所谓的注册监听器,其实就是把ApplicationListener的实现类放入一个LinkedHashSet的集合,此处没有任何与消息类型相关的操作,因此,监听器注册的时候并没有将消息类型和监听器绑定;

金手指:defaultRetriever是一个LinkedHashSet

4.2 发送广播的时候是否存在监听器和消息类型绑定?

监听器注册的代码看过了,没有绑定,那么只好去看广播消息的代码了,好在前面才看过广播的代码,印象还算深刻,再次来到SimpleApplicationEventMulticaster的multicastEvent方法:

金手指:刚刚只看到multicastEvent方法,没有进去里面看,现在进去multicastEvent里面看一看

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));//根据消息类型取出对应的所有监听器for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {Executor executor = getTaskExecutor();if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {invokeListener(listener, event);}}
}

如上述代码所示,解开我们疑问的关键点就在getApplicationListeners(event, type)这段代码了,也就是说在发送消息的时候根据类型去找所有对应的监听器,展开这个方法一探究竟:

金手指:进入getApplicationListeners方法看一看

protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {Object source = event.getSource();Class<?> sourceType = (source != null ? source.getClass() : null);//缓存的key有两个维度:消息来源+消息类型(关于消息来源可见ApplicationEvent构造方法的入参)ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);// retrieverCache是ConcurrentHashMap对象,所以是线程安全的,// ListenerRetriever中有个监听器的集合,并有些简单的逻辑封装,调用它的getApplicationListeners方法返回的监听类集合是排好序的(order注解排序)ListenerRetriever retriever = this.retrieverCache.get(cacheKey);if (retriever != null) {//如果retrieverCache中找到对应的监听器集合,就立即返回了return retriever.getApplicationListeners();}if (this.beanClassLoader == null ||(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {//如果retrieverCache中没有数据,就在此查出数据并放入缓存,//先加锁synchronized (this.retrievalMutex) {//双重判断的第二重,避免自己在BLOCK的时候其他线程已经将数据放入缓存了retriever = this.retrieverCache.get(cacheKey);if (retriever != null) {return retriever.getApplicationListeners();}//新建一个ListenerRetriever对象retriever = new ListenerRetriever(true);//retrieveApplicationListeners方法复制找出某个消息类型加来源类型对应的所有监听器Collection<ApplicationListener<?>> listeners =retrieveApplicationListeners(eventType, sourceType, retriever);//存入retrieverCache  this.retrieverCache.put(cacheKey, retriever);//返回结果return listeners;}}else {// No ListenerRetriever caching -> no synchronization necessaryreturn retrieveApplicationListeners(eventType, sourceType, null);}
}

上述代码解开了所有疑惑:在广播消息的时刻,如果某个类型的消息在缓存中找不到对应的监听器集合,就调用retrieveApplicationListeners方法去找出符合条件的所有监听器,然后放入这个集合;这和之前的猜测匹配度挺高的…

retrieveApplicationListeners方法在面对各种监听器的时候处理逻辑过于复杂,就不在这里展开了;

至此,我们对广播和监听的原理已经有了较全面的认识,可以动手实战了;

五、开发一个自定义广播

现在基于maven创建一个SpringBoot的web工程customizeapplicationevent,在工程中自定义一个发送广播的bean,在收到一个web请求时,利用该bean发出广播;

  1. 创建工程customizeapplicationevent,pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.bolingcavalry</groupId><artifactId>customizeapplicationevent</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>customizeapplicationevent</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
  1. 自定义一个消息类型CustomizeEvent:
package com.bolingcavalry.customizeapplicationevent.event;import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;/*** @Description : 自定义的消息类型* @Author : zq2599@gmail.com* @Date : 2018-08-16 06:09*/
public class CustomizeEvent extends ApplicationContextEvent {public CustomizeEvent(ApplicationContext source) {super(source);}
}
  1. 自定义广播发送者CustomizePublisher,除了用到ApplicationEventPublisher,还要用到ApplicationContext(构造CustomizeEvent对象的时候要用),所以要实现两个Aware接口:
package com.bolingcavalry.customizeapplicationevent.publish;import com.bolingcavalry.customizeapplicationevent.event.CustomizeEvent;
import com.bolingcavalry.customizeapplicationevent.util.Utils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;/*** @Description : 自定义的广播发送器* @Author : zq2599@gmail.com* @Date : 2018-08-16 06:09*/
@Service
public class CustomizePublisher implements ApplicationEventPublisherAware, ApplicationContextAware {private ApplicationEventPublisher applicationEventPublisher;private ApplicationContext applicationContext;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {this.applicationEventPublisher = applicationEventPublisher;Utils.printTrack("applicationEventPublisher is set : " + applicationEventPublisher);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}/*** 发送一条广播*/public void publishEvent(){applicationEventPublisher.publishEvent(new CustomizeEvent(applicationContext));}
}
  1. 做一个controller,这样就能通过web请求来触发一次广播了:
package com.bolingcavalry.customizeapplicationevent.controller;import com.bolingcavalry.customizeapplicationevent.publish.CustomizePublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.Date;/*** @Description : 收到web请求后发送一条广播* @Author : zq2599@gmail.com* @Date : 2018-08-16 06:45*/
@RestController
public class CustomizePublishEventController {@Autowiredprivate CustomizePublisher customizePublisher;@RequestMapping("/publish")public String publish(){customizePublisher.publishEvent();return "publish finish, "+ new Date();}
}

自定义广播的代码已经完成了,不过现在即使把消息发出去了也不能验证是否发送成功,先不要运行工程,接下来把自定义消息监听也做了吧;

六、开发两个自定义监听器

我们要做两个自定义监听器;

  1. 第一个监听类在泛型中定义的类型是ApplicationEvent,即接收所有广播消息;
package com.bolingcavalry.customizeapplicationevent.listener;import com.bolingcavalry.customizeapplicationevent.util.Utils;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;/*** @Description : 自定义的系统广播监听器,接收所有类型的消息* @Author : zq2599@gmail.com* @Date : 2018-08-15 14:53*/
@Service
public class AllEventListener implements ApplicationListener<ApplicationEvent>{@Overridepublic void onApplicationEvent(ApplicationEvent event) {//为了便于了解调用栈,在日志中打印当前堆栈Utils.printTrack("onApplicationEvent : " + event);}
}
  1. 第二个监听类在泛型中定义的类型是CustomizeEvent,即只接收CustomizeEvent类型的消息;
package com.bolingcavalry.customizeapplicationevent.listener;import com.bolingcavalry.customizeapplicationevent.event.CustomizeEvent;
import com.bolingcavalry.customizeapplicationevent.util.Utils;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Service;/*** @Description : 自定义的系统广播监听器,只接受CustomizeEvent类型的消息* @Author : zq2599@gmail.com* @Date : 2018-08-15 14:53*/
@Service
public class CustomizeEventListener implements ApplicationListener<CustomizeEvent>{@Overridepublic void onApplicationEvent(CustomizeEvent event) {//为了便于了解调用栈,在日志中打印当前堆栈    Utils.printTrack("onApplicationEvent : " + event);}
}
  1. 把工程运行起来,先见到CustomizePublisher被注入ApplicationEventPublisher对象的日志,然后看到AllEventListener收到广播后打印的日志,如下所示,可通过日志中的堆栈信息印证我们之前看过的广播监听相关源码:

  2. 在浏览器输入地址:http://localhost:8080/publish,触发一次CustomizeEvent类型的消息广播,日志显示CustomizeEventListener和AllEventListener都收到了消息,如下:

实战完成,自定义的广播发送和监听都达到了预期;

七、金手指

7.1 金手指1:ApplicationContext发布广播publisherEvent()方法谁调用?

7.1.1 事件的触发事件,从四个事件到自定义事件

回答:模拟的时候开发者自己触发,实际的时候ApplicationContext自己触发,ApplicationContext有四个事件

举一反三 在这里插入图片描述

在这里插入图片描述

在这里插入图片描述 在这里插入图片描述

7.1.2 事件类的定义:从四个事件到自定义事件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
自定义事件类

在这里插入图片描述

7.1.3 事件监听类的定义:从四个事件监听类到自定义事件监听类

在这里插入图片描述

金手指2:事件发布三要素:listener event publisher

listener与event如何关联起来?
listener有一个泛型,这个泛型就是具体的Event,且看上面图片
interface ApplicationListener

publisher与event如何关联起来?
publishEvent()方法接收 event参数

listener实现的接口为:ApplicationListener
listener中的方法为:onApplicationEvent(),在这个方法里面,执行监听到自己的泛型及其子类事件后(因为E extends XxxEvent),应该执行的逻辑

event实现的接口为:extends ApplicationContextEvent
event中的方法:没有方法

publisher实现的接口为:XxxAware接口 implements ApplicationEventPublisherAware, ApplicationContextAware
publisher中的方法:XxxAware的实现方法,和发布事件的具体方法

金手指3:自己模拟的事件,自己发布

运行结果:自己模拟的事件,自己发布

启动的时候的事件发布

启动的时候,执行AbstractApplicationContext类中的finishRefresh()方法中的
this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this))); 语句,
发布一个ApplicationEvent事件,并被AllEventListener监听器监听到(该监听器监听ApplicationEvent及其子类事件),执行onApplicationContext()方法

调用controller接口的时候的事件发布

启动的时候,执行CustomizePublisher类中的publishEvent()方法中的
applicationEventPublisher.publishEvent(new CustomizeEvent(applicationContext)); 语句,发布一个CustomizeEvent事件,并被AllEventListener监听器(该监听器监听ApplicationEvent及其子类事件)和CustomizeEventListener监听器(该监听器监听CustomizeEvent及其子类事件)监听到,执行onApplicationEvent()方法

金手指4:彻底总结

在这里插入图片描述

事件监听者

ApplicationListener是从哪里来的,是从Javase这里来的
在这里插入图片描述

事件监听者为什么要使用注解成为bean

源码依据 要在源码中找到 if (instance of beanFactory)

在这里插入图片描述

事件监听者实现的接口如何确定

事件监听者需要实现ApplicationListener接口,泛型为自己要监听的事件及其子类

@Service
public class AllEventListener implements ApplicationListener<ApplicationEvent>{
@Service
public class CustomizeEventListener implements ApplicationListener<CustomizeEvent> {

源码依据:事件监听者为什么要实现ApplicationListener接口

key:在Spring中去找if(xxx instanceof ApplicationListener)

找到了

在这里插入图片描述

因为接收的时候,只有bean是ApplicationListener及其子类的实现类的实例化对象的时候,才添加该listener,所有我们自定义的listener一定要实现ApplicationListener接口,同样,因为接收的时候,只有bean是ApplicationListener及其子类的实现类的实例化对象的时候,才添加该listener,所有,Spring自带的listener也一定要实现ApplicationListener接口

源码依据:泛型为自己要监听的事件及其子类

为什么监听到的是自己的泛型及其子类事件,

key:到源码中找接收消息,绑定事件类型的代码

这里不是真正原因,这是只是表示ApplicationListener的泛型必须是ApplicationEvent或者其子类
在这里插入图片描述

小结:为什么自定义listener和Spring自带listener要实现ApplicationListener接口,因为接收消息的时候要调用 必须是ApplicationListener接口及其子类实例化对象
bash if (bean instanceof ApplicationListener) {

事件监听者实现的方法如何确定(key:在Spring找事件监听者listener调用的那个方法)

实现了ApplicationListener接口,就要重写onApplicationEvent()为什么要重写这个方法,

第一,ApplicationListener接口的方法是onApplicationEvent;

第二,发送消息的时候要调用 listener.onApplicationEvent(event);(第二点是关键,因为底层Spring要调用这个方法)

两者,所有实现了ApplicationListener接口就要实现onApplicationEvent方法
在这里插入图片描述

在这里插入图片描述

小结:为什么自定义listener和Spring自带listener要实现onApplicationEvent()方法,因为publisher发布消息的时候要调用 listener.onApplicationEvent(event);

事件本体

事件发布者

ApplicationEventPublisher从哪里来的,自然而来,不继承任何接口

在这里插入图片描述

@Service
public class CustomizePublisher implements ApplicationEventPublisherAware, ApplicationContextAware {

为什么要使用@Service注解注入到SpringIOC容器

为什么要实现XxxAware接口

八、小结

Spirng事件发布与接收(三要素:event listener publisher) 完成

天天打码,天天进步!!!

  相关解决方案