文章目录
- 一、前言
- 二、源码分析一(发布事件publisher和监听事件listener)
- 三、源码分析二(接收事件)
- 四、源码分析三(如何做到只接收指定类型的消息/Spring底层接收指定类型广播)
- 五、开发一个自定义广播
- 六、开发两个自定义监听器
- 七、金手指
- 八、小结
一、前言
二、源码分析一(发布事件publisher和监听事件listener)
金手指:如何广播一条消息:从AbstractApplicationContext类的finishRefresh方法开始
ApplicationEventPublisher类
2.1 ApplicationEventPublisher接口:广播服务的核心能力
提到spring容器中的广播和监听,就不得不先看看ApplicationEventPublisher这个接口,今天的故事都是围绕此君展开:
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
// 通知所有匹配的listner
void publishEvent(Object event);
}
金手指:publishEvent()方法通知所有匹配的listner(并不是一个),publishEvent()一定存在一个循环
从上述代码可以看出,发送消息实际上就是在调用ApplicationEventPublisher 接口实现类的publishEvent方法,接下来看看类图,了解在spring容器的设计中ApplicationEventPublisher的定位:
从上图可见,ApplicationContext继承了ApplicationEventPublisher,那么ApplicationContext的实现类自然也就具备了发送广播的能力,按照这个思路去看一下关键的抽象类AbstractApplicationContext,果然找到了方法实现:
AbstractApplicationContext类
@Override
public void publishEvent(ApplicationEvent event) {
publishEvent(event, null);
}
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
// 装饰事件(实参event)
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
// 如果可以马上广播,如果不可以,直到初始化再广播
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// 广播它的parent
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}
2.2 如何广播一条消息(refresh()最后一个方法finishRefresh()广播一条消息)
来看一个具体的广播的例子吧,spring容器初始化结束的时候,会执行AbstractApplicationContext类的finishRefresh方法,里面就会广播一条代表初始化结束的消息,代码如下:
protected void finishRefresh() {
clearResourceCaches(); // 清空资源缓存
initLifecycleProcessor(); // 初始化生命周期处理器
getLifecycleProcessor().onRefresh(); // 首先,广播生命周期处理器
// 广播一条消息,这条消息类型为ContextRefreshedEvent代表spring容器初始化结束
publishEvent(new ContextRefreshedEvent(this));
LiveBeansView.registerApplicationContext(this); // 参与LiveBeansView MBean
}
继续深入publichEvent方法内部,看到的代码如下图所示:
上图中的绿色部分的判断不成立,初始化过程中的registerListeners方法就会把成员变量earlyApplicationEvents设置为空,因此广播消息执行的是getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType),在ApplicationContext的实现类中,真正的广播操作交给了成员变量applicationEventMulticaster来完成,没有特殊配置的话,applicationEventMulticaster是SimpleApplicationEventMulticaster类型的实例(详情请看initApplicationEventMulticaster方法);
打开的源码,如下所示,是否有种豁然开朗的感觉?广播的实现是如此简单明了,找到已注册的ApplicationListener,逐个调用invokeListener方法,将ApplicationListener和事件作为入参传进去就完成了广播;
@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);
}
}
}
所以,笔者刚才说通知监听该事件的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是个ConcurrentHashMap
ListenerRetriever 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 necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
金手指,源码使用HashMap,不需要线程安全;
源码使用LinkedHashMap,需要保证插入顺序;
源码中使用TreeMap,尚未见过,自定义线程安全
源码中使用ConcurrentHashMap,线程安全,源码中不会使用HashTable,效率太低
//retrieverCache是个ConcurrentHashMap 所以这里要考虑线程安全
看到这里,我们对spring容器内的广播与监听已经有所了解,小结如下:
- 广播的核心接口是ApplicationEventPublisher;ApplicationContext继承继承了这个接口。
- 从发起一条广播到监听器响应广播全部过程:refresh()中的finishRefresh()中的,publishEvent(new ContextRefreshedEvent(this));发送一条广播,这条消息类型为ContextRefreshedEvent代表spring容器初始化结束;最终交给listner来处理。
接下来还不能马上动手实战,我们还有两件事情要想弄清楚:
- 具备发送能力:我想发自定义的广播消息,如何具备这个发送能力?
- 具备接收能力:我想接收特定的广播,如何具备这个接收能力?
接下来,继续结合spring源码去找上面两个问题的答案;
2.3 问题1:具备发送能力?ApplicationEventPublisherAware
Spring发送广播:ApplicationEventPublisherAware
具备发送能力:
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在实例化之后要做的事情:
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof ApplicationListener) {
// potentially not detected as a listener by getBeanNamesForType retrieval
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
//注册监听器,其实就是保存在成员变量applicationEventMulticaster的成员变量defaultRetriever的集合applicationListeners中
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
}
else if (Boolean.FALSE.equals(flag)) {
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源码中,但是看源码之前先自己猜测一下,自定义监听器只接收指定类型的消息,以下两种方案都可以实现:
- 接收广播时监听器与消息类型绑定:注册监听器的时候,将监听器和消息类型绑定;
- 发送广播时监听器与消息类型绑定:广播的时候,按照这条消息的类型去找指定了该类型的监听器,但不可能每条广播都去所有监听器里面找一遍,应该是说广播的时候会触发一次监听器和消息的类型绑定;
4.1 接收广播的时候是否存在监听器和消息类型绑定?错误
带着上述猜测去spring源码中寻找答案吧,先看注册监听器的代码,按照之前的分析,注册监听发生在后置处理器ApplicationListenerDetector中,看看this.applicationContext.addApplicationListener这一行代码的内部逻辑(金手指:刚才接收的广播的时候我们没有进来看,现在我们进来addApplicationListener方法中看一看):
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
if (this.applicationEventMulticaster != null) {
// 注册监听器
this.applicationEventMulticaster.addApplicationListener(listener);
}
else {
this.applicationListeners.add(listener);
}
}
最终跟踪到AbstractApplicationEventMulticaster类的addApplicationListener方法:
@Override
public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) {// retrievalMutex作为同步锁对象
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();
}
}
从以上代码我们发现了两个关键点:
- AOP是通过代理技术实现的,此时可能通过CGLIB生成了监听类的代理类,此类的实例如果也被注册到监听器集合中,那么广播的时候,按照消息类型就会取出两个监听器实例来了,到时候的效果就是一个消息被两个实例消费了,因此这里要先清理掉代理类;
- 所谓的注册监听器,其实就是把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方法看一看,刚才没有进来这里看,刚才发送广播就是到invokeListener()这里了
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource(); // 事件的源
Class<?> sourceType = (source != null ? source.getClass() : null); // 消息来源
//构建cachekey,缓存的key有两个维度:消息来源+消息类型(关于消息来源可见ApplicationEvent构造方法的入参)
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// retrieverCache是ConcurrentHashMap对象,所以是线程安全的,
// ListenerRetriever中有个监听器的集合,并有些简单的逻辑封装,调用它的getApplicationListeners方法返回的监听类集合是排好序的(order注解排序)
ListenerRetriever retriever = this.retrieverCache.get(cacheKey); //map中根据key得到value
if (retriever != null) {
//如果retrieverCache中找到对应的监听器集合,就立即返回了
return retriever.getApplicationListeners();
}
// 这里表示retriever == null,没从map里面拿到,开始双层if+synchronized判断
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
//如果retrieverCache中没有数据,就在此查出数据并放入缓存,
//先加锁
synchronized (this.retrievalMutex) { // retrievalMutex作为锁对象
//双重判断的第二重,避免自己在BLOCK的时候其他线程已经将数据放入缓存了
retriever = this.retrieverCache.get(cacheKey); // concurrenthashmap,从cachekey得到
if (retriever != null) {
return retriever.getApplicationListeners(); // 拿到了,直接返回,结束
}
//这里表示retriever == null,没从map里面拿到,新建一个ListenerRetriever对象
retriever = new ListenerRetriever(true);
//retrieveApplicationListeners方法复制找出某个消息类型加来源类型对应的所有监听器
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
//存入retrieverCache
this.retrieverCache.put(cacheKey, retriever);
//返回结果
return listeners;
}
}
else {
// 在广播消息的时刻,如果某个类型的消息在缓存中找不到对应的监听器集合,就调用retrieveApplicationListeners方法去找出符合条件的所有监听器,然后放入这个集合
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
上述代码解开了所有疑惑:在广播消息的时刻,如果某个类型的消息在缓存中找不到对应的监听器集合,就调用retrieveApplicationListeners方法去找出符合条件的所有监听器,然后放入这个集合;这和之前的猜测匹配度挺高的…
在广播消息的时刻,如果某个类型的消息在缓存中找不到对应的监听器集合,就调用retrieveApplicationListeners方法去找出符合条件的所有监听器,然后放入这个集合。
return retrieveApplicationListeners(eventType, sourceType, null);
retrieveApplicationListeners方法在面对各种监听器的时候处理逻辑过于复杂,就不在这里展开了;
至此,我们对广播和监听的原理已经有了较全面的认识,可以动手实战了;
五、开发一个自定义广播
现在基于maven创建一个SpringBoot的web工程customizeapplicationevent,在工程中自定义一个发送广播的bean,在收到一个web请求时,利用该bean发出广播;
- 创建工程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>
- 自定义一个消息类型CustomizeEvent:
package com.bolingcavalry.customizeapplicationevent.event;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ApplicationContextEvent;
// 自定义事件,继承ApplicationContextEvent
public class CustomizeEvent extends ApplicationContextEvent {
public CustomizeEvent(ApplicationContext source) {
super(source);
}
}
- 自定义广播发送者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;
// 自定义发送器,XxxAware,就是在refresh()第三个方法调用
@Service
public class CustomizePublisher implements ApplicationEventPublisherAware, ApplicationContextAware {
private ApplicationEventPublisher applicationEventPublisher;
private ApplicationContext applicationContext;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
Utils.printTrack("applicationEventPublisher is set : " + applicationEventPublisher);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* 发送一条广播
*/
public void publishEvent(){
applicationEventPublisher.publishEvent(new CustomizeEvent(applicationContext)); // refresh()中调用两个set,然后浏览器发送get请求在发送广播 publishEvent(),
// 调用者:
// 参数:new CustomizeEvent(applicationContext) 表示发送的事件
}
}
- 做一个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;
// get请求就发送广播
@RestController
public class CustomizePublishEventController {
@Autowired
private CustomizePublisher customizePublisher;
@RequestMapping("/publish")
public String publish(){
customizePublisher.publishEvent();
return "publish finish, "
+ new Date();
}
}
自定义广播的代码已经完成了,不过现在即使把消息发出去了也不能验证是否发送成功,先不要运行工程,接下来把自定义消息监听也做了吧;
六、开发两个自定义监听器
我们要做两个自定义监听器;
- 第一个监听类在泛型中定义的类型是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;
//自定义的系统广播监听器,接收所有类型的消息,接收ApplicationEvent及其子类关系
@Service
public class AllEventListener implements ApplicationListener<ApplicationEvent>{
@Override
public void onApplicationEvent(ApplicationEvent event) {
//为了便于了解调用栈,在日志中打印当前堆栈
Utils.printTrack("onApplicationEvent : " + event);
}
}
小结:自定义listener接收那种类型的消息,就是通过泛型指定的
金手指:事件发布三要素:listener event publisher
相关问题1:listener与event如何关联起来?
回答1:listener有一个泛型,这个泛型就是具体的Event,且看上面图片
interface ApplicationListener
表示这个listener类监听的事件,就是这个泛型类及其子类
相关问题2:publisher与event如何关联起来?
回答2:publishEvent()方法接收 event参数,表示这个publisher类发布哪个事件
- 第二个监听类在泛型中定义的类型是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;
自定义的系统广播监听器,只接受CustomizeEvent类及其子类的消息
@Service
public class CustomizeEventListener implements ApplicationListener<CustomizeEvent>{
@Override
public void onApplicationEvent(CustomizeEvent event) {
//为了便于了解调用栈,在日志中打印当前堆栈
Utils.printTrack("onApplicationEvent : " + event);
}
}
- 把工程运行起来,先见到CustomizePublisher被注入ApplicationEventPublisher对象的日志,然后看到AllEventListener收到广播后打印的日志,如下所示,可通过日志中的堆栈信息印证我们之前看过的广播监听相关源码:
- 在浏览器输入地址:http://localhost:8080/publish,触发一次CustomizeEvent类型的消息广播,日志显示CustomizeEventListener和AllEventListener都收到了消息,如下:
实战完成,自定义的广播发送和监听都达到了预期;
七、金手指
7.1 ApplicationContext发布广播publisherEvent()方法谁调用?
7.1.1 事件广播类的定义,从四个事件到自定义事件
回答:模拟的时候开发者自己触发,实际的时候ApplicationContext自己触发,ApplicationContext有四个事件
举一反三
小结:AbstractApplicationContext是一个事件广播器
在finishRefresh()方法中,发布ContextRefreshedEvent事件,该事件使用this实参初始化
在doClose()方法中,发布ContextClosedEvent事件,该事件使用this实参初始化
在start()方法中,发布ContextStartedEvent事件,该事件使用this实参初始化
在stop()方法中,发布ContextStoppedEvent事件,该事件使用this实参初始化
7.1.2 事件类的定义:从四个事件到自定义事件
小结:一共四个事件类
ContextRefreshedEvent事件,接收ApplicationContext类型参数
ContextClosedEvent事件,接收ApplicationContext类型参数
ContextStartedEvent事件,接收ApplicationContext类型参数
ContextStoppedEvent事件,接收ApplicationContext类型参数
自定义事件类
7.1.3 事件监听类的定义:从四个事件监听类到自定义事件监听类
注意:发送事件和接收事件到底在干什么?(特别重要)
模拟中,发送事件就是调用一下publisher,没有东西打印,listener接收事件调用onApplicationContext()打印日志,看模拟代码就明白。这个onApplicationContext()是不用手动调用的,自动调用 good,从模拟代码就可以知道
实际中,四个事件发送、四个事件,也是这样,直接发送,不用完成什么逻辑,listener接收事件在调用onApplicationContext()完成相关逻辑,自己看代码就知道。这个onApplicationContext()是不用手动调用的,自动调用 good,从实际代码就可以知道(2.2 doInvokeListener()方法中listener.onApplicationEvent(event);)
Aware接口:事件发布者实现Aware接口,只是为了初始化applicationContextPublisher和applicationContext,为发送事件做准备,不是直接发送事件,直接发送事件是直接调用publish()方法
7.2 事件发布三要素:listener event publisher(特别重要)
listener与event如何关联起来?
listener有一个泛型,这个泛型就是具体的Event,且看上面图片
interface ApplicationListener
publisher与event如何关联起来?
publishEvent()方法接收 event参数,表示发送的事件类型,模拟的时候
publishEvent((ApplicationEvent)new XxxEvent(applicationContext))
publishEvent(new XxxEvent(this)) good 参数就搞懂了
publishEvent()方法调用者一定是一个ApplicationEventPublisher及其子类,模拟的时候是使用applicationEventPublisher.publishEvent(),实际AbstractApplicationContext类中是使用this.publishEvent() 实际搞懂,但是模拟没搞懂,这个applicationEventPublisher是如何赋值的?((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); 所以这个applicationEventPublisher就是具体的this.applicaitonContext
listener实现的接口为:ApplicationListener
listener中的方法为:onApplicationEvent(),在这个方法里面,执行监听到自己的泛型及其子类事件后(因为E extends XxxEvent),应该执行的逻辑
event实现的接口为:extends ApplicationContextEvent
event中的方法:没有方法
publisher实现的接口为:XxxAware接口 implements ApplicationEventPublisherAware, ApplicationContextAware
publisher中的方法:XxxAware的实现方法,和发布事件的具体方法
7.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()方法
7.4 彻底总结
7.4.1 事件监听者
ApplicationListener是从哪里来的,是从Javase这里来的
7.4.1.1 事件监听者为什么要使用注解成为bean
源码依据 要在源码中找到 if (instance of beanFactory)
7.4.1.2 事件监听者实现的接口如何确定
事件监听者需要实现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) {
7.4.1.3 事件监听者实现的方法如何确定(key:在Spring找事件监听者listener调用的那个方法)
实现了ApplicationListener接口,就要重写onApplicationEvent()为什么要重写这个方法,
第一,ApplicationListener接口的方法是onApplicationEvent;
第二,发送消息的时候要调用 listener.onApplicationEvent(event);(第二点是关键,因为底层Spring要调用这个方法)
两者,所有实现了ApplicationListener接口就要实现onApplicationEvent方法
小结:为什么自定义listener和Spring自带listener要实现onApplicationEvent()方法,因为publisher发布消息的时候要调用 listener.onApplicationEvent(event);
7.4.2 事件本体
7.4.3 事件发布者
ApplicationEventPublisher从哪里来的,自然而来,不继承任何接口
@Service
public class CustomizePublisher implements ApplicationEventPublisherAware, ApplicationContextAware {
7.4.3.1 为什么要使用@Service注解注入到SpringIOC容器
7.4.3.2 为什么要实现XxxAware接口
所有,只有实现了
public void publishEvent(){
applicationEventPublisher.publishEvent(new CustomizeEvent(applicationContext));
}
自定义事件的构造函数构造实例需要一个ApplicationContext类型变量,通过实现了ApplicationContextAware接口,让spring容器给我们构造一个ApplicationContext类型变量;
发布事件需要调用publishEvent方法,需要一个ApplicationEventPublisher类型变量,通过时实现ApplicationEventPublisherAware接口,让spring容器给我吗构造一个ApplicationEventPublisher类型变量;
有了两个实例化变量,就可以发布事件了。
至于为什么自定义事件发布类要使用注解变为bean:还没解决。。。。。。。
7.5 附加:Spring事件广播机制
7.5.1 第一个问题:具备发送能力XxxAware接口
我们可以创建一个bean,实现了ApplicationEventPublisherAware接口,那么该bean的setApplicationEventPublisher方法就会被调用,通过该方法可以接收到ApplicationEventPublisher类型的入参,借助这个ApplicationEventPublisher就可以发消息了;
7.5.2 第二个问题:具备接收能力listener
如果当前bean实现了ApplicationListener接口(金手指:就是代码中的 if (bean instanceof ApplicationListener) { ),就会调用this.applicationContext.addApplicationListener方法将当前bean注册到applicationContext的监听器集合中,后面有广播就直接找到这些监听器,调用每个监听器的onApplicationEvent方法( 金手指:就是上面的doInvokeListener方法中的 listener.onApplicationEvent(event); );
7.5.3 第三个问题:事件发送时,监听器与消息类型的绑定
在广播消息的时刻,如果某个类型的消息在缓存中找不到对应的监听器集合,就调用retrieveApplicationListeners方法去找出符合条件的所有监听器,然后放入这个集合。
return retrieveApplicationListeners(eventType, sourceType, null);
7.5.4 第四个问题:Spring 容器事件发布的局限
局限1:Spring的容器内事件发布功能只适合在单一容器内的简单消息通知和处理,对于分布式、多进程和多容器之间的时间通知并不擅长;
局限2:ApplicationListener只通过void onApplicationEvent(ApplicationEvent event)这一个事件处理方法来处理事件,所以要在事件类中尽量保存必要的信息;
八、小结
Spirng事件发布与接收(三要素:event listener publisher) 完成
天天打码,天天进步!!!