深入理解Spring的容器内事件发布监听机制

时间:2022-09-14 07:27:20

在讲解事件监听机制前,我们先回顾下设计模式中的观察者模式,因为事件监听机制可以说是在典型观察者模式基础上的进一步抽象和改进。我们可以在JDK或者各种开源框架比如Spring中看到它的身影,从这个意义上说,事件监听机制也可以看做一种对传统观察者模式的具体实现,不同的框架对其实现方式会有些许差别。

典型的观察者模式将有依赖关系的对象抽象为了观察者和主题两个不同的角色,多个观察者同时观察一个主题,两者只通过抽象接口保持松耦合状态,这样双方可以相对独立的进行扩展和变化:比如可以很方便的增删观察者,修改观察者中的更新逻辑而不用修改主题中的代码。但是这种解耦进行的并不彻底,这具体体现在以下几个方面:

  • 1.抽象主题需要依赖抽象观察者,而这种依赖关系完全可以去除。
  • 2.主题需要维护观察者列表,并对外提供动态增删观察者的接口,
  • 3.主题状态改变时需要由自己去通知观察者进行更新。

我们可以把主题(Subject)替换成事件(event),把对特定主题进行观察的观察者(Observer)替换成对特定事件进行监听的监听器(EventListener),而把原有主题中负责维护主题与观察者映射关系以及在自身状态改变时通知观察者的职责从中抽出,放入一个新的角色事件发布器(EventPublisher)中,事件监听模式的轮廓就展现在了我们眼前,如下图所示

深入理解Spring的容器内事件发布监听机制

常见事件监听机制的主要角色如下

  • 事件及事件源:对应于观察者模式中的主题。事件源发生某事件是特定事件监听器被触发的原因。
  • 事件监听器:对应于观察者模式中的观察者。监听器监听特定事件,并在内部定义了事件发生后的响应逻辑。
  • 事件发布器:事件监听器的容器,对外提供发布事件和增删事件监听器的接口,维护事件和事件监听器之间的映射关系,并在事件发生时负责通知相关监听器。

Spring框架对事件的发布与监听提供了相对完整的支持,它扩展了JDK中对自定义事件监听提供的基础框架,并与Spring的IOC特性作了整合,使得用户可以根据自己的业务特点进行相关的自定义,并依托Spring容器方便的实现监听器的注册和事件的发布。因为Spring的事件监听依托于JDK提供的底层支持,为了更好的理解,先来看下JDK中为用户实现自定义事件监听提供的基础框架。

2. JDK中对事件监听机制的支持

JDK为用户实现自定义事件监听提供了两个基础的类。一个是代表所有可被监听事件的事件基类java.util.EventObject,所有自定义事件类型都必须继承该类,类结构如下所示

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    /**
* The object on which the Event initially occurred.
*/
protected transient Object source; /**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @exception IllegalArgumentException if source is null.
*/
public EventObject(Object source) {
if (source == null)
throw new IllegalArgumentException("null source"); this.source = source;
} /**
* The object on which the Event initially occurred.
*
* @return The object on which the Event initially occurred.
*/
public Object getSource() {
return source;
} /**
* Returns a String representation of this EventObject.
*
* @return A a String representation of this EventObject.
*/
public String toString() {
return getClass().getName() + "[source=" + source + "]";
}
}

该类内部有一个Object类型的source变量,逻辑上表示发生该事件的事件源,实际中可以用来存储包含该事件的一些相关信息。

另一个则是对所有事件监听器进行抽象的接口java.util.EventListener,这是一个标记接口,内部没有任何抽象方法,所有自定义事件监听器都必须实现该标记接口

/**
* A tagging interface that all event listener interfaces must extend.
* @since JDK1.1
*/
public interface EventListener {
}

以上就是JDK为我们实现自定义事件监听提供的底层支持。针对具体业务场景,我们通过扩展java.util.EventObject来自定义事件类型,同时通过扩展java.util.EventListener来定义在特定事件发生时被触发的事件监听器。当然,不要忘了还要定义一个事件发布器来管理事件监听器并提供发布事件的功能。

2.1 基于JDK实现对任务执行结果的监听

想象我们正在做一个关于Spark的任务调度系统,我们需要把任务提交到集群中并监控任务的执行状态,当任务执行完毕(成功或者失败),除了必须对数据库进行更新外,还可以执行一些额外的工作:比如将任务执行结果以邮件的形式发送给用户。这些额外的工作后期还有较大的变动可能:比如还需要以短信的形式通知用户,对于特定的失败任务需要通知相关运维人员进行排查等等,所以不宜直接写死在主流程代码中。最好的方式自然是以事件监听的方式动态的增删对于任务执行结果的处理逻辑。为此我们可以基于JDK提供的事件框架,打造一个能够对任务执行结果进行监听的弹性系统。

  • 任务结束事件的事件源

    因为要对任务执行结束这一事件进行监听,所以必须对任务这一概念进行定义,如下
/**
* @author: takumiCX
* @create: 2018-11-02
**/
@Data
public class Task { private String name; private TaskFinishStatus status; }

任务包含任务名和任务状态,其中任务状态是个枚举常量,只有成功和失败两种取值。

/**
* @author: takumiCX
* @create: 2018-11-02
**/
public enum TaskFinishStatus {
SUCCEDD,
FAIL;
}
  • 任务结束事件TaskFinishEvent

    自定义事件类型TaskFinishEvent继承自JDK中的EventObject,构造时会传入Task作为事件源。
/**
* @author: takumiCX
* @create: 2018-11-02
**/
public class TaskFinishEvent extends EventObject {
/**
* Constructs a prototypical Event.
*
* @param source The object on which the Event initially occurred.
* @throws IllegalArgumentException if source is null.
*/
public TaskFinishEvent(Object source) {
super(source);
}
}
  • 该事件的监听器抽象

    继承标记接口EventListner表示该接口的实现类是一个监听器,同时在内部定义了事件发生时的响应方法onTaskFinish(event),接收一个TaskFinishEvent作为参数。
/**
* @author: takumiCX
* @create: 2018-11-02
**/
public interface TaskFinishEventListner extends EventListener { void onTaskFinish(TaskFinishEvent event);
}
  • 邮件服务监听器

    该邮件服务监听器将在监听到任务结束事件时将任务的执行结果发送给用户。
/**
* @author: takumiCX
* @create: 2018-11-03
**/
@Data
public class MailTaskFinishListener implements TaskFinishEventListner { private String emial; @Override
public void onTaskFinish(TaskFinishEvent event) {
System.out.println("Send Emial to "+emial+" Task:"+event.getSource());
}
}
  • 自定义事件发布器
/**
* @author: takumiCX
* @create: 2018-11-03
**/
public class TaskFinishEventPublisher { private List<TaskFinishEventListner> listners=new ArrayList<>(); //注册监听器
public synchronized void register(TaskFinishEventListner listner){
if(!listners.contains(listner)){
listners.add(listner);
}
} //移除监听器
public synchronized boolean remove(TaskFinishEventListner listner){
return listners.remove(listner);
} //发布任务结束事件
public void publishEvent(TaskFinishEvent event){ for(TaskFinishEventListner listner:listners){
listner.onTaskFinish(event);
}
}
}
  • 测试代码如下
/**
* @author: takumiCX
* @create: 2018-11-03
**/
public class TestTaskFinishListener { public static void main(String[] args) { //事件源
Task source = new Task("用户统计", TaskFinishStatus.SUCCEDD); //任务结束事件
TaskFinishEvent event = new TaskFinishEvent(source); //邮件服务监听器
MailTaskFinishListener mailListener = new MailTaskFinishListener("takumiCX@163.com"); //事件发布器
TaskFinishEventPublisher publisher = new TaskFinishEventPublisher(); //注册邮件服务监听器
publisher.register(mailListener); //发布事件
publisher.publishEvent(event); }
}
  • 测试结果

    深入理解Spring的容器内事件发布监听机制

如果后期因为需求变动需要在任务结束时将结果以短信的方式发送给用户,则可以再添加一个短信服务监听器

/**
* @author: takumiCX
* @create: 2018-11-03
**/
@Data
@AllArgsConstructor
public class SmsTaskFinishListener implements TaskFinishEventListner { private String address; @Override
public void onTaskFinish(TaskFinishEvent event) {
System.out.println("Send Message to "+address+" Task:"+event.getSource());
}
}

在测试代码中添加如下代码向事件发布器注册该监听器

SmsTaskFinishListener smsListener = new SmsTaskFinishListener("123456789");

//注册短信服务监听器
publisher.register(smsListener);

最后运行结果如下

深入理解Spring的容器内事件发布监听机制

基于JDK的支持要实现对自定义事件的监听还是比较麻烦的,要做的工作比较多。而且自定义的事件发布器也不能提供对所有事件的统一发布支持。基于Spring框架实现自定义事件监听则要简单很多,功能也更加强大。

3.Spring容器对事件监听机制的支持

Spring容器,具体而言是ApplicationContext接口定义的容器提供了一套相对完善的事件发布和监听框架,其遵循了JDK中的事件监听标准,并使用容器来管理相关组件,使得用户不用关心事件发布和监听的具体细节,降低了开发难度也简化了开发流程。下面看看对于事件监听机制中的各主要角色,Spring框架中是如何定义的,以及相关的类体系结构

  • 事件

    Spring为容器内事件定义了一个抽象类ApplicationEvent,该类继承了JDK中的事件基类EventObject。因而自定义容器内事件除了需要继承ApplicationEvent之外,还要传入事件源作为构造参数。

    深入理解Spring的容器内事件发布监听机制

  • 事件监听器

    Spring定义了一个ApplicationListener接口作为为事件监听器的抽象,接口定义如下

public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

   /**
* Handle an application event.
* @param event the event to respond to
*/
void onApplicationEvent(E event); }

1.该接口继承了JDK中表示事件监听器的标记接口EventListener,内部只定义了一个抽象方法onApplicationEvent(evnt),当监听的事件在容器中被发布,该方法将被调用。

2.同时,该接口是一个泛型接口,其实现类可以通过传入泛型参数指定该事件监听器要对哪些事件进行监听。这样有什么好处?这样所有的事件监听器就可以由一个事件发布器进行管理,并对所有事件进行统一发布,而具体的事件和事件监听器之间的映射关系,则可以通过反射读取泛型参数类型的方式进行匹配,稍后我们会对原理进行讲解。

3.最后,所有的事件监听器都必须向容器注册,容器能够对其进行识别并委托容器内真正的事件发布器进行管理。

  • 事件发布器

    ApplicationContext接口继承了ApplicationEventPublisher接口,从而提供了对外发布事件的能力,如下所示

    深入理解Spring的容器内事件发布监听机制

那么是否可以说ApplicationContext,即容器本身就担当了事件发布器的角色呢?其实这是不准确的,容器本身仅仅是对外提供了事件发布的接口,真正的工作其实是委托给了具体容器内部一个ApplicationEventMulticaster对象,其定义在AbstractApplicationContext抽象类内部,如下所示

/** Helper class used in event publishing */
private ApplicationEventMulticaster applicationEventMulticaster;

所以,真正的事件发布器是ApplicationEventMulticaster,这是一个接口,定义了事件发布器需要具备的基本功能:管理事件监听器以及发布事件。其默认实现类是

SimpleApplicationEventMulticaster,该组件会在容器启动时被自动创建,并以单例的形式存在,管理了所有的事件监听器,并提供针对所有容器内事件的发布功能。

3.1 基于Spring实现对任务执行结果的监听

业务场景在2.1中已经介绍过了,这里就不在啰嗦。基于Spring框架来实现对自定义事件的监听流程十分简单,只需要三部:1.自定义事件类 2.自定义事件监听器并向容器注册 3.发布事件

  • 1.自定任务结束事件

    定义一个任务结束事件TaskFinishEvent2,该类继承抽象类ApplicationEvent来遵循容器事件规范。
/**
* @author: takumiCX
* @create: 2018-11-04
**/
public class TaskFinishEvent2 extends ApplicationEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public TaskFinishEvent2(Object source) {
super(source);
}
}
  • 2.自定义邮件服务监听器并向容器注册

    该类实现了容器事件规范定义的监听器接口,通过泛型参数指定对上面定义的任务结束事件进行监听,通过@Component注解向容器进行注册
/**
* @author: takumiCX
* @create: 2018-11-04
**/
@Component
public class MailTaskFinishListener2 implements ApplicationListener<TaskFinishEvent2> { private String emial="takumiCX@163.com"; @Override
public void onApplicationEvent(TaskFinishEvent2 event) { System.out.println("Send Emial to "+emial+" Task:"+event.getSource()); }
}
  • 3.发布事件

    从上面对Spring事件监听机制的类结构分析可知,发布事件的功能定义在ApplicationEventPublisher接口中,而ApplicationContext继承了该接口,所以最好的方法是通过实现ApplicationContextAware接口获取ApplicationContext实例,然后调用其发布事件方法。如下所示定义了一个发布容器事件的代理类
/**
* @author: takumiCX
* @create: 2018-11-04
**/
@Component
public class EventPublisher implements ApplicationContextAware { private ApplicationContext applicationContext; //发布事件
public void publishEvent(ApplicationEvent event){ applicationContext.publishEvent(event);
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext=applicationContext;
}
}

在此基础上,还可以自定义一个短信服务监听器,在任务执行结束时发送短信通知用户。过程和上面自定义邮件服务监听器类似:实现ApplicationListner接口并重写抽象方法,然后通过注解或者xml的方式向容器注册。

4.Spring事件监听源码解析

Spring事件监听机制离不开容器IOC特性提供的支持,比如容器会自动创建事件发布器,自动识别用户注册的监听器并进行管理,在特定的事件发布后会找到对应的事件监听器并对其监听方法进行回调。Spring帮助用户屏蔽了关于事件监听机制背后的很多细节,使用户可以专注于业务层面进行自定义事件开发。然而我们还是忍不住对其背后的实现原理进行一番探讨,比如:

  • 1.事件发布器ApplicationEventMulticaster是何时被初始化的,初始化过程中都做了什么?
  • 2.注册事件监听器的过程是怎样的,容器怎么识别出它们并进行管理?
  • 3.容器发布事件的流程是怎样的?它如何根据发布的事件找到对应的事件监听器,事件和由该事件触发的监听器之间的匹配规则是怎样的?

为了对以上问题进行解答,我们不得不深入源码层面一探究竟。

4.1 初始化事件发布器流程

真正的事件发布器是ApplicationEventMulticaster,它定义在AbstractApplicationContext中,并在ApplicationContext容器启动的时候进行初始化。在容器启动的refrsh()方法中可以找到初始化事件发布器的入口方法,如下图所示

深入理解Spring的容器内事件发布监听机制

protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 判断beanFactory里是否定义了id为applicationEventMulticaster的bean,默认是没有的
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isDebugEnabled()) {
logger.debug("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
//一般情况会走这里,创建一个SimpleApplicationEventMulticaster并交由容器管理
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isDebugEnabled()) {
logger.debug("Unable to locate ApplicationEventMulticaster with name '" +
APPLICATION_EVENT_MULTICASTER_BEAN_NAME +
"': using default [" + this.applicationEventMulticaster + "]");
}
}
}

这里会根据核心容器beanFactory中是否有id为applicationEventMulticaster的bean分两种情况:

  • 1.容器中已有id为applicationEventMulticaster的bean

    直接从容器缓存获取或是创建该bean实例,并交由成员变量applicationEventMulticaster保存。

    当用户自定义了事件发布器并向容器注册时会执行该流程。

  • 2.容器中不存在applicationEventMulticaster的bean

    这是容器默认的执行流程,会创建一个SimpleApplicationEventMulticaster,其仅在实现事件发布器基本功能(管理事件监听器以及发布容器事件)的前提下,增加了可以设置任务执行器

    Executor和错误处理器ErrorHandler的功能,当设置Executor为线程池时,则会以异步的方式对事件监听器进行回调,而ErrorHandler允许我们在回调方法执行错误时进行自定义处理。默认情况下,

    这两个变量都为null。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

   private Executor taskExecutor;

   private ErrorHandler errorHandler;
public abstract class AbstractApplicationEventMulticaster
implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware { private final ListenerRetriever defaultRetriever = new ListenerRetriever(false); final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64); private ClassLoader beanClassLoader; private BeanFactory beanFactory; private Object retrievalMutex = this.defaultRetriever;

之后会调用beanFactory.registerSingleton方法将创建的SimpleApplicationEventMulticaster实例注册为容器的单实例bean。

初始化事件发布器的工作非常简单,一句话总结:由容器实例化用户自定义的事件发布器或者由容器帮我们创建一个简单的事件发布器并交由容器管理。

4.2 注册事件监听器流程

注册事件监听器的流程在初始化事件发布器之后,如下图所示

深入理解Spring的容器内事件发布监听机制

其关键代码如下所示

// uninitialized to let post-processors apply to them!
//获取实现ApplicationListener接口的所有bean的beanName
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
//将监听器的beanName保存到事件发布器中
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}

首先遍历beanFactory中所有的bean,获取所有实现ApplicationListener接口的bean的beanName,并将这些beanName注册到ApplicationEventMulticaster中

@Override
public void addApplicationListenerBean(String listenerBeanName) {
synchronized (this.retrievalMutex) {
//保存所有监听器的beanName
this.defaultRetriever.applicationListenerBeans.add(listenerBeanName);
//清除维护事件和监听器映射关系的缓存
this.retrieverCache.clear();
}
}

defaultRetriever是定义在抽象类AbstractApplicationEventMulticaster中的成员,用来保存所有事件监听器及其beanName

private final ListenerRetriever defaultRetriever = new ListenerRetriever(false);

其以内部类形式定义在AbstractApplicationEventMulticaster中

	/**
* Helper class that encapsulates a specific set of target listeners,
* allowing for efficient retrieval of pre-filtered listeners.
* <p>An instance of this helper gets cached per event type and source type.
*/
private class ListenerRetriever { //保存所有事件监听器
public final Set<ApplicationListener<?>> applicationListeners; //保存所有事件监听器的beanName
public final Set<String> applicationListenerBeans;

向事件发布器注册监听器的beanName,其实就是将beanName加入ListenerRetriever的集合中。

其实看到这里会有一个疑问,registerListeners()方法只是找到了所有监听器的beanName并将其保存到了事件发布器ApplicationEventMulticaster中,那么真正的事件监听器实例是何时被创建并被加入到事件发布器中的?

这里我们不得不退回到启动容器的refresh()方法中,在初始化beanFactory之后,初始化事件发布器之前,容器在prepareBeanFactory(beanFactory)方法中又注册了一些重要组件,其中就包括一个特殊的BeanPostProcessor:ApplicationListenerDetector,正如其类名暗示的那样,这是一个事件监听器的探测器。

深入理解Spring的容器内事件发布监听机制

深入理解Spring的容器内事件发布监听机制

该类实现了BeanPostProcessor接口,如下图所示

深入理解Spring的容器内事件发布监听机制

ApplicationListenerDetector实现了BeanPostProcessor接口,可以在容器级别对所有bean的生命周期过程进行增强。这里主要是为了能够在初始化所有bean后识别出所有的事件监听器bean并

将其注册到事件发布器中

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) { //判断该bean是否实现了ApplicationListener接口
if (this.applicationContext != null && bean instanceof ApplicationListener) {
// potentially not detected as a listener by getBeanNamesForType retrieval
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
// singleton bean (top-level or inner): register on the fly
//将实现了ApplicationListener接口的bean注册到事件发布器applicationEventMulticaster中
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 events
logger.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后,该ApplicationListenerDetector的postProcessAfterInitialization(Object bean, String beanName)方法会被作用在每一个bean上,通过判断传入的bean

是否是ApplicationListener实例进行过滤,然后将找到的事件监听器bean注册到事件发布器中。

4.3 容器事件发布流程

深入理解Spring的容器内事件发布监听机制

深入理解Spring的容器内事件发布监听机制

这里为了简化源码阅读的工作量,对一些细节和分支情形做了忽略,只考虑主流程,如上图箭头所示,这里调用了事件发布器的multicastEvent()方法进行事件发布,需要传入事件event和事件类型

eventType作为参数。不过通常这个eventType参数为null,因为事件的类型信息完全可以通过反射的方式从event对象中获得。继续跟进源码

@Override
public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
//获取事件类型
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
//遍历所有和事件匹配的事件监听器
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
//获取事件发布器内的任务执行器,默认该方法返回null
Executor executor = getTaskExecutor();
if (executor != null) {
//异步回调监听方法
executor.execute(new Runnable() {
@Override
public void run() {
invokeListener(listener, event);
}
});
}
else {
//同步回调监听方法
invokeListener(listener, event);
}
}
}

首先通过传入的参数或者通过调用resolveDefaultEventType(event)方法获取事件的事件类型信息,之后会通过

getApplicationListeners(event, type)方法得到所有和该事件类型匹配的事件监听器,其实现逻辑后面会细说,这里先跳过。对于匹配的每一个监听器,视事件发布器内是否设置了

任务执行器实例Executor,决定以何种方式对监听器的监听方法进行回调。

  • 若执行器实例Executor未设置,则进行同步回调,即在当前线程执行监听器的回调方法
  • 若用户设置了Executor实例(通常而言是线程池),则会进行异步回调,监听器的监听方法会交由线程池中的线程去执行。

    默认情况下容器不会为用户创建执行器实例,因而对监听器的回调是同步进行的,即所有监听器的监听方法都在推送事件的线程中被执行,通常这也是处理业务逻辑的线程,若其中一个监听器回调执行

    阻塞,则会阻塞整个业务处理的线程,造成严重的后果。而异步回调的方式,虽然不会导致业务处理线程被阻塞,但是不能共享一些业务线程的上下文资源,比如类加载器,事务等等。因而究竟选择哪种回调

    方式,要视具体业务场景而定。

好了,现在可以来探究下困扰我们很久的一个问题了,那就是:如何根据事件类型找到匹配的所有事件监听器?这部分逻辑在getApplicationListeners方法中

protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
//获取事件中的事件源对象
Object source = event.getSource();
//获取事件源类型
Class<?> sourceType = (source != null ? source.getClass() : null);
//以事件类型和事件源类型为参数构建一个cacheKey,用于从缓存map中获取与之匹配的监听器列表
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType); // Quick check for existing entry on 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)))) {
// Fully synchronized building and caching of a ListenerRetriever
synchronized (this.retrievalMutex) {
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
retriever = new ListenerRetriever(true);
//查找所有与发布事件匹配的监听器列表
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
//将匹配结果缓存到map中
this.retrieverCache.put(cacheKey, retriever);
return listeners;
}
}
else {
// No ListenerRetriever caching -> no synchronization necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}

整个流程可以概括为:

  • 1.首先从缓存map中查找,这个map定义在事件发布器的抽象类中
final Map<ListenerCacheKey, ListenerRetriever> retrieverCache =
new ConcurrentHashMap<ListenerCacheKey, ListenerRetriever>(64);

ListenerCacheKey由事件类型eventType和事件源类型sourceType构成,ListenerRetriever内部则维护了一个监听器列表。当所发布的事件类型和事件源类型与Map中的key匹配时,

将直接返回value中的监听器列表作为匹配结果,通常这发生在事件不是第一次发布时,能避免遍历所有监听器并进行过滤,如果事件时第一次发布,则会执行流程2。

  • 2.遍历所有的事件监听器,并根据事件类型和事件源类型进行匹配。
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
/**
* Actually retrieve the application listeners for the given event and source type.
* @param eventType the event type
* @param sourceType the event source type
* @param retriever the ListenerRetriever, if supposed to populate one (for caching purposes)
* @return the pre-filtered list of application listeners for the given event and source type
*/
private Collection<ApplicationListener<?>> retrieveApplicationListeners(
ResolvableType eventType, Class<?> sourceType, ListenerRetriever retriever) { //这是存放匹配的监听器的列表
LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>();
Set<ApplicationListener<?>> listeners;
Set<String> listenerBeans;
synchronized (this.retrievalMutex) {
listeners = new LinkedHashSet<ApplicationListener<?>>(this.defaultRetriever.applicationListeners);
listenerBeans = new LinkedHashSet<String>(this.defaultRetriever.applicationListenerBeans);
}
//遍历所有的监听器
for (ApplicationListener<?> listener : listeners) {
//判断该事件监听器是否匹配
if (supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListeners.add(listener);
}
//将匹配的监听器加入列表
allListeners.add(listener);
}
}
//这部分可以跳过
if (!listenerBeans.isEmpty()) {
BeanFactory beanFactory = getBeanFactory();
for (String listenerBeanName : listenerBeans) {
try {
Class<?> listenerType = beanFactory.getType(listenerBeanName);
if (listenerType == null || supportsEvent(listenerType, eventType)) {
ApplicationListener<?> listener =
beanFactory.getBean(listenerBeanName, ApplicationListener.class);
if (!allListeners.contains(listener) && supportsEvent(listener, eventType, sourceType)) {
if (retriever != null) {
retriever.applicationListenerBeans.add(listenerBeanName);
}
allListeners.add(listener);
}
}
}
catch (NoSuchBeanDefinitionException ex) {
// Singleton listener instance (without backing bean definition) disappeared -
// probably in the middle of the destruction phase
}
}
}
//对匹配的监听器列表进行排序
AnnotationAwareOrderComparator.sort(allListeners);
return allListeners;
}

判断监听器是否匹配的逻辑在supportsEvent(listener, eventType, sourceType)中,

protected boolean supportsEvent(ApplicationListener<?> listener, ResolvableType eventType, Class<?> sourceType) {
//对原始的ApplicationListener进行一层适配器包装成为GenericApplicationListener
GenericApplicationListener smartListener = (listener instanceof GenericApplicationListener ?
(GenericApplicationListener) listener : new GenericApplicationListenerAdapter(listener));
//判断监听器是否支持该事件类型以及该事件源类型
return (smartListener.supportsEventType(eventType) && smartListener.supportsSourceType(sourceType));
}

首先对原始的ApplicationListener进行一层适配器包装成GenericApplicationListener,便于后面使用该接口中定义的方法判断监听器是否支持传入的事件类型或事件源类型

public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

   /**
* Determine whether this listener actually supports the given event type.
*/
boolean supportsEventType(ResolvableType eventType); //判断是否支持该事件类型 /**
* Determine whether this listener actually supports the given source type.
*/
boolean supportsSourceType(Class<?> sourceType); //判断是否支持该事件源类型 }

smartListener.supportsEventType(eventType)用来判断监听器是否支持该事件类型,因为我们的监听器实例通常都不是SmartApplicationListener类型,所以直接看下面箭头所指的方法就好

深入理解Spring的容器内事件发布监听机制

declaredEventType是监听器泛型的实际类型,而eventType是发布的事件的类型

declaredEventType.isAssignableFrom(eventType)当以下两种情况返回true

  • 1.declaredEventType和eventType类型相同
  • 2.declaredEventType是eventType的父类型

    只要监听器泛型的实际类型和发布的事件类型一样或是它的父类型,则该监听器将被成功匹配。

而对于事件源类型而言,通常默认会直接返回true,也就是说事件源的类型通常对于判断匹配的监听器没有意义。

深入理解Spring的容器内事件发布监听机制

这里查找到所有匹配的监听器返回后会将匹配关系缓存到retrieverCache这个map中

				Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
//将匹配结果缓存到map中
this.retrieverCache.put(cacheKey, retriever);
return listeners;

梳理下容器事件发布的整个流程,可以总结如下

深入理解Spring的容器内事件发布监听机制

5.总结

这篇文章主要是为了梳理下Spring的容器内事件体系并对其工作原理做一定程度上的源码上的剖析,本来还想展示一些关于Spring事件发布监听机制的一些扩展特性和额外功能,不过由于前面废话太多导致篇幅

已经大大超出了预期,后面这部分只能有时间另开一篇再说了。

深入理解Spring的容器内事件发布监听机制的更多相关文章

  1. 【spring源码学习】spring的事件发布监听机制源码解析

    [一]相关源代码类 (1)spring的事件发布监听机制的核心管理类:org.springframework.context.event.SimpleApplicationEventMulticast ...

  2. Spring笔记&lpar;7&rpar; - Spring的事件和监听机制

    一.背景 事件机制作为一种编程机制,在很多开发语言中都提供了支持,同时许多开源框架的设计中都使用了事件机制,比如SpringFramework. 在 Java 语言中,Java 的事件机制参与者有3种 ...

  3. 【cocos2d-js官方文档】事件分发监听机制(摘录)

    简介 游戏开发中一个很重要的功能就是交互,如果没有与用户的交互,那么游戏将变成动画,而处理用户交互就需要使用事件监听器了. 总概: 事件监听器(cc.EventListener) 封装用户的事件处理逻 ...

  4. jsonp和事件发布监听

    模拟jsonp var id = 0; function JSONP(url,param,cb){ var callbackName = "json_" + id++; var a ...

  5. 【sping揭秘】8、容器内部事件发布(一)

    容器内部事件发布 Spring的applicationContext容器提供的容器内事件发布功能,是通过java提供的自定义事件实现的 事件类型:eventObject 类继承 事件监听:eventL ...

  6. 【sping揭秘】9、容器内部事件发布(二)

    写在前面---------------------------------- 命运多舛,痴迷淡然 不知下一步该往哪里走,现在应该是我的迷茫期... 加油,快点走出去!!! 聪明的网友们,你们有没有迷茫 ...

  7. Spring事件发布与监听机制

    我是陈皮,一个在互联网 Coding 的 ITer,微信搜索「陈皮的JavaLib」第一时间阅读最新文章,回复[资料],即可获得我精心整理的技术资料,电子书籍,一线大厂面试资料和优秀简历模板. 目录 ...

  8. Spring 事件监听机制及原理分析

    简介 在JAVA体系中,有支持实现事件监听机制,在Spring 中也专门提供了一套事件机制的接口,方便我们实现.比如我们可以实现当用户注册后,给他发送一封邮件告诉他注册成功的一些信息,比如用户订阅的主 ...

  9. Spring ApplicationContext(八)事件监听机制

    Spring ApplicationContext(八)事件监听机制 本节则重点关注的是 Spring 的事件监听机制,主要是第 8 步:多播器注册:第 10 步:事件注册. public void ...

随机推荐

  1. CI框架如何在主目录application目录之外使用uploadify上传插件和bootstrap前端框架:

    19:29 2016/3/10CI框架如何在主目录application目录之外使用uploadify上传插件和bootstrap前端框架:项目主路径:F:\wamp\www\graduationPr ...

  2. WebApi 接口测试工具:WebApiTestClient

    文章来源:http://www.cnblogs.com/landeanfen/p/5210356.html 一.WebApiTestClient介绍 1.WebApiTestClient组件作用主要有 ...

  3. 在Egret实现二维码长按识别

      Egret中二维码图片,是在canvas上,无法在微信上长按扫描识别. 由于微信长按识别二维码是截屏扫描原理,所以只要长按当前屏幕任意一张图片,都能够识别当前屏幕上的二维码. 这里把二维码放在ex ...

  4. EventKit 学习(译)

    From:http://docs.xamarin.com/guides/ios/platform_features/introduction_to_eventkit/ 本教程展示了对于如何通过Even ...

  5. CDH 的Cloudera Manager免费与收费版的对比表

    CDH 特性 免费版 付费版 Deployment, Configuration & Management 系统管理 Automated Deployment & Hadoop Rea ...

  6. UVa 12558 - Egyptian Fractions &lpar;HARD version&rpar;

    题目大意: 给出一个真分数,把它分解成最少的埃及分数的和.同时给出了k个数,不能作为分母出现,要求解的最小的分数的分母尽量大. 分析: 迭代加深搜索,求埃及分数的基础上,加上禁用限制就可以了.具体可以 ...

  7. 利用C&num;实现对excel的写操作

    一.COM interop 首先我们要了解下何为COM Interop,它是一种服务,可以使.NET Framework对象能够与COM对象通信.Visual Studio .NET 通过引入面向公共 ...

  8. QT5&period;8 for embedded

    http://doc.qt.io/qt-5/embedded-linux.html 先占座~

  9. 使用linq语句进行联表查询

    假设你有一个父表(例如:汽车),其关联一个子表,例如*(一对多).现在你想对于所有的父表汽车,遍历所有汽车,然后打印出来所有*的信息.默认的做法将是: SELECT CarId FROM Cars ...

  10. Java中的包扫描(工具)

    在现在好多应用场景中,我们需要得到某个包名下面所有的类, 包括我们自己在src里写的java类和一些第三方提供的jar包里的类,那么怎么来实现呢? 今天带大家来完成这件事. 先分享代码: 1.这个类是 ...