[Spring]详解Spring中的事件监听器模式

1. 事件监听器模式的重要因素

  • Event Object: 事件,事件源会将事件进行发布。Spring中的事件对象为ApplicationEvent.
  • Event Listener: 事件监听器,负责处理订阅的事件. Spring中对应的事件监听器接口为ApplicationListener.
  • Event Source: 事件源,负责发布事件并通知事件监听器。Spring中对应的事件源接口为ApplicationEventPublisher.

关于事件监听器模式,如果你不够熟悉,可以在我的上篇博客得到解答->点我前往

2. ApplicationEvent

2.1 接口清单

值得一提的是,Spring中的ApplicationEvent是内置事件源的,这意味着在监听器中可以获取事件源,而Spring中的事件源通常为容器本身.
同时,事件发生的同时,会记录当前系统时间戳

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.context;

import java.util.EventObject;

/**
 * Class to be extended by all application events. Abstract as it
 * doesn't make sense for generic events to be published directly.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 */
public abstract class ApplicationEvent extends EventObject {

    /** use serialVersionUID from Spring 1.2 for interoperability. */
    private static final long serialVersionUID = 7099057708183571937L;

    /** System time when the event happened. */
    private final long timestamp;


    /**
     * Create a new ApplicationEvent.
     * @param source the object on which the event initially occurred (never {@code null})<br>
     * 创建一个应用事件,其中source为事件源,并且不能为空.
     */
    public ApplicationEvent(Object source) {
        super(source);
        this.timestamp = System.currentTimeMillis();
    }


    /**
     * Return the system time in milliseconds when the event happened.<br>
     * 返回当前事件发生时的系统时间
     */
    public final long getTimestamp() {
        return this.timestamp;
    }

}

2.2 UML

UML

看类图可以知道,ApplicationEvent继承自JDK的EventObject,同时还有一个抽象子类ApplicationContextEvent,为什么要再抽象出这个子类呢,因为Object本身的含义太过广泛,Spring为了定义容器事件,强制约束source对象本身的类型为ApplicationContext.
最后从这个抽象类,衍生了一系列单一职责的事件。分别对应容器的关闭、刷新、启动、停止等阶段.

2.3 PayloadApplicationEvent

Spring 4.2 后推出一个一个基于泛型对事件进行包装的类,在此之前,发布的Event都必须继承自ApplicationEvent.加入Payload机制后,在发布事件的时候,可以传输任意的Object,Spring内部都会用PayloadApplicationEvent对事件进行包装.

具体的改进在org.springframework.context.ApplicationEventPublisher#publishEvent(java.lang.Object)可以做更加深入的了解。

default

3. ApplicationListener

3.1 接口清单

函数式接口,同时监听的事件需为继承自ApplicationEvent的类,如果Spring为4.2后的,可以不受这个限制,因为内部使用PayloadApplicationEvent进行了包装,在事件源发布事件时,会触发onApplicationEvent通知监听器。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

    /**
     * Handle an application event.
     * @param event the event to respond to
     * 处理应用事件
     */
    void onApplicationEvent(E event);

}

3.2 UML

UML
  • SmartApplicationListener: 支持事件消费排序与事件类型匹配,Spring3.0开始支持,需要实现的方法为boolean supportsEventType(Class<? extends ApplicationEvent> eventType);,注意这里推断的对象是Class.
  • GenericApplicationListener: 支持事件消费排序与事件类型匹配,Spring4.2开始支持,需要实现的方法为boolean supportsEventType(ResolvableType eventType);,注意这里推断的对象是ResolvableType.

3.3 注解支持-@EventListener

Spring4.2后,对事件监听器增加了注解的支持,无需实现接口,只需要通过@EventListener来直接在需要响应的方法上标注即可,配合Async@Order注解还可以支持异步与消费顺序声明.

注意,注解标注的方法上,最好声明为void,如果返回类型为数组或者集合,Spring会将每个元素作为新的事件进行发布。

4.ApplicationEventPublisher&ApplicationEventMulticaster

4.1 ApplicationEventPublisher

4.1.1 接口清单

通常在SpringIOC中,容器本身会作为ApplicationEventPublisher去进行事件的发布.
同时,开发者还可以通过Aware接口访问到ApplicationEventPublisher实例.来发布自定义事件.

@FunctionalInterface
public interface ApplicationEventPublisher {
    // 通知所有与此应用程序注册的匹配侦听器一个应用程序事件。
    // 事件可以是框架事件(例如ContextRefreshedEvent)或特定于应用程序的事件。
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    // 通知所有与此应用程序注册的匹配侦听器事件。
    // 如果指定的事件不是ApplicationEvent,则将其包装在PayloadApplicationEvent中。
    void publishEvent(Object event);

}
4.1.2 UML
UML

这里再次验证,容器即为IOC的ApplicationEventPublisher.

4.2 ApplicationEventMulticaster

Spring中的publisher只提供了发布事件的接口,然而一个事件监听器模式少不了注册监听器这件事情,ApplicationEventMulticaster就是为了解决这件事而产生的。

4.2.1 接口清单
public interface ApplicationEventMulticaster {

    void addApplicationListener(ApplicationListener<?> listener);

    void addApplicationListenerBean(String listenerBeanName);

    void removeApplicationListener(ApplicationListener<?> listener);

    void removeApplicationListenerBean(String listenerBeanName);

    void removeAllListeners();

    void multicastEvent(ApplicationEvent event);

    void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);

}

从接口清单我们可以看到,ApplicationEventMulticaster支持添加监听器、移除监听器、发布事件。

4.2.2 UML
UML
  • AbstractApplicationEventMulticaster: 内部持有一个defaultRetriever成员变量,该变量为ListenerRetriever,其内部使用Set存储ApplicationListenerAbstractApplicationEventMulticaster添加监听器的操作为this.defaultRetriever.applicationListeners.add(listener);.有兴趣的读者可以自行扩展阅读.
  • SimpleApplicationEventMulticaster: 继承自AbstractApplicationEventMulticaster,内置private Executor taskExecutor;-任务执行器。默认情况下,监听器都以同步的方式进行,但是会由于个别监听器速度过慢,导致任务进度阻塞,因此该事件发布器也支持了以线程池来提交异步任务的方式消费事件.
    public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            // 如果线程池不为空,则使用线程池提交任务.
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            }
            else {
                invokeListener(listener, event);
            }
        }
    }

4.3 为什么要同时定义ApplicationEventMulticaster和ApplicationEventPublisher?

org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)中,我们可以看到,容器内部是怎么发送事件的.

    protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
        Assert.notNull(event, "Event must not be null");

        // Decorate event as an ApplicationEvent if necessary
        ApplicationEvent applicationEvent;
        if (event instanceof ApplicationEvent) {
            applicationEvent = (ApplicationEvent) event;
        }
        else {
            // 如果事件对象不是ApplicationEvent,则使用PayloadApplicationEvent进行包装
            applicationEvent = new PayloadApplicationEvent<>(this, event);
            if (eventType == null) {
                eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
            }
        }

        // Multicast right now if possible - or lazily once the multicaster is initialized
        if (this.earlyApplicationEvents != null) {
            this.earlyApplicationEvents.add(applicationEvent);
        }
        else {
            // 重点,可以看到publishEvent其实是通过ApplicationEventMulticaster进行真正的事件发布
            getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
        }

        // Publish event via parent context as well...
        // 一些递归的操作
        if (this.parent != null) {
            if (this.parent instanceof AbstractApplicationContext) {
                ((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
            }
            else {
                this.parent.publishEvent(event);
            }
        }
    }

简单地说,就是ApplicationEventPublisher是一个简单的事件发布接口,只负责声明入口,而ApplicationEventMulticaster负责处理真正的事件发布逻辑。这其实是一种委托的思想.你可以在Spring随处发现这种现象,如Registry接口其实真正的执行者为DefaultListableBeanFactory.

总结

  • Spring中的事件监听器模式扩展自JDK中的事件监听器模型。
  • 事件在早期版本必须继承自ApplicationEvent,4.2后Spring引入了PayloadApplicationEvent进行兼容,支持以Object的形式发送事件.
  • 事件源在IOC中通常为容器本身.
  • 事件监听器支持实现接口和注解形式进行实现,同时可以使用@Order注解来指定消费顺序。
  • 避免在事件监听器中调用事件源的发布事件,引起循环引用的问题。
  • Spring以委托的思想,建立了事件发布者的接口视图->ApplicationEventPublisher,其中真正的执行者则为ApplicationEventMulticaster接口的实现类.
  • ApplicationEventMulticaster提供了异步的支持。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容