Spring5IOC容器解析——事件监听机制

一、事件驱动模型简介

事件驱动模型,也即是我们通常说的观察者。基于发布-订阅模式的编程模型。

概念

定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖它的对象都得到通知并自动更新。

百度百科:
从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。
事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。
事件发送器负责将收集器收集到的事件分发到目标对象中。
事件处理器做具体的事件响应工作。

从程序设计的角度来看,事件驱动模型的核心构件通常包含以下几个:

  • 事件源(Event Source):负责产生事件的对象。比如我们常见的按钮,按钮就是一个事件源,能够产生“点击”这个事件
  • 事件监听器(Event Listener):注册在事件源上才能被调用,主要用于监听事件并进行事件处理或者转发。
  • 事件对象(Event Object):或者称为事件对象,是事件源和事件监听器之间的信息桥梁。是整个事件模型驱动的核心

下图展示了事件、事件源、监听器直接的关系:


1、 观察者模式

原理解析

观察者模式的UML图如下:


具体类说明如下:

  • Observer观察者:即为事件模型中的事件监听器,该接口定义一个方法update,即为事件处理方法。当观察到有事件产生时,该方法便会处理
  • Subject被观察者:即事件模型中的事件源,负责产生事件,定义与观察者建立关联的方法(添加观察者、删除观察者、通知观察者)
  • ConreteObserver具体的观察者实现类:实现Observer接口中的update方法,具体实例会被添加到被观察者的观察者队列中(observers[List])
  • ConreteSubject具体的被观察者实现类:实现Subject接口。定义观察者队列(observers[List]),并定义实现如何将观察者对象添加到观察者队列中以及如何通所有知观察者

在上述类图中,具体的ConreteSubject被观察者,其中包含observers一个列表,保存所有观察者对象。doAction方法是需要通知观察者对象的动作,当该方法执行后,会通知保存在observers中的所有观察者。

也就是在执行方法ConreteSubject#doAction()时,需要调用ConreteSubject#notifyObservers()通知保存在observers中的所有观察者,让其能够做出响应。

Spring 中的事件监听机制

Spring 中的事件

Spring 中的事件通知机制就是观察者模式的一种实现。观察者是 ApplicationListener,可以实现接口定义观察者,也可以使用注解定义观察者。观察者感兴趣的是某种状态的变化,这种状态变化使用 ApplicationEvent 来传达,也就是事件对象。我们说的 Spring 中的事件,就是 ApplicationEvent。在事件中,被观察者可以认为是发出事件的一方,只有在状态变化时才发布事件。

当有状态发生变化时,发布者调用 ApplicationEventPublisher 的 publishEvent 方法发布一个事件,Spring 容器广播事件给所有观察者,调用观察者的 onApplicationEvent 方法把事件对象传递给观察者。调用 publishEvent 方法有两种途径,一种是实现接口由容器注入 ApplicationEventPublisher 对象然后调用其方法,另一种是直接调用容器的方法,两种方法发布事件没有太大区别。

相关类型总结如下:

  • ApplicationListener:事件监听者,观察者;
  • ApplicationEvent:Spring 事件,记录事件源、时间和数据;
  • ApplicationEventPublisher:发布事件;
优势

使用事件可以解耦代码,观察者与被观察者可以分开开发,中间只有事件作为联系,不用关心另一方如何实现。观察者可以有多个,所以对于同一个事件可以有多种不同的处理方式,不过要确保不依赖处理的顺序。使用事件后,观察者可以单独开发,对主流程没有任何影响,可以简化主流程的开发。

事件可以用于各种场景的消息通知,比如系统收到停机指令后,可以发出停机事件,这样相关的子系统就可以进行停机前的各种处理。那么 Spring 中的事件是如何实现的呢?

Spring事件驱动模型的三大组成部分

  • ApplicationEvent:事件对象,Spring事件驱动模型中的对象源,继承JDK EventObject,通过在发布事件时通过EventObject.source字段携带事件相关的数据。

  • ApplicationListener:应用监听器,负责监听事件对象是否有发生变化,实现该接口并实现onApplicationEvent方法,完成事件发生变化时的逻辑处理

  • ApplicationEventPublisher:事件发布器,定义了事件发布规范,只定义了接口,具体的实现交由其他类中实现。Spring提供了SimpleApplicationEventMulticaster实现了广播事件发布器。

Spring 框架提供了四种容器事件,我们可以直接观察,包括:

  • ContextStartedEvent:ApplicationContext 启动事件。
  • ContextRefreshedEvent:ApplicationContext 更新事件。
  • ContextStoppedEvent:ApplicationContext 停止事件。
  • ContextClosedEvent:ApplicationContext 关闭事件。

Spring 4.2 之前的版本,事件必须继承 ApplicationEvent,从 Spring 4.2 版开始,框架提供了 PayloadApplicationEvent 用于包装任意类型,不强制事件对象继承 ApplicationEvent。当我们发送一个任意类型的事件对象时,框架内部自动包装为 PayloadApplicationEvent 事件对象。

事件监听者

事件监听者 ApplicationListener 继承自 JDK 的 EventListener ,JDK 要求所有监听者继承它。监听者只有一个方法 onApplicationEvent,用来处理事件 ApplicationEvent。

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

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

}

在容器启动的时候检测应用中的监听者并把用户实现的监听者注册到 SimpleApplicationEventMulticaster 集合中。

Spring 也支持直接注解的形式进行事件监听,使用注解 @EventListener 即可。使用注解时,方法可以返回任意类型,如果返回值不为 void 则当做一个新事件再次发布。

@Service
public class AnnoEventListener {
    @EventListener
    public void listen(MyEvent myEvent){
        System.out.println("receive " + myEvent.getSource());
    }
}

注解形式的监听者是通过 EventListenerMethodProcessor 注册到容器中的。该类定义了一个 Bean,在初始化完成后,调用它的后置回调方法 afterSingletonsInstantiated,在方法中遍历容器中所有的 bean,提取出 EventListener 注解修饰的方法并根据注解的参数创建 ApplicationListener 对象加入到 ApplicationContext 监听列表中。

发布事件

ApplicationEventPublisher 接口定义了事件发布方法,代码如下:

@FunctionalInterface
public interface ApplicationEventPublisher {

    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    void publishEvent(Object event);
}

ApplicationContext 接口继承了 ApplicationEventPublisher ,并在 AbstractApplicationContext 实现了具体代码,实际执行是委托给 ApplicationEventMulticaster。

ApplicationEventMulticaster 接口定义了对监听者的操作,如增加监听者、移除监听者,以及发布事件的方法。框架提供了 ApplicationEventMulticaster 接口的默认实现SimpleApplicationEventMulticaster,如果本地容器中没有 ApplicationEventMulticaster 的实现就会使用这个默认实现。

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);
}

在 SimpleApplicationEventMulticaster 中,可以看到 multicastEvent 方法中遍历了所有的 Listener,并依次调用 Listener 的 onApplicationEvent 方法发布事件。Spring 还提供了异步发布事件的能力,taskExecutor 不为 null 时即异步执行。

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {

    @Nullable
    private Executor taskExecutor;

    @Override
    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);
            }
        }
    }
}

注意事项:
可以看到,Spring 处理事件是同步的,监听者的执行时间最好不要太长,以免影响主流程。如果事件的处理一定要耗费比较长的时间,请使用异步方式进行处理。

高级监听者

SmartApplicationListener

SmartApplicationListener 接口是 ApplicationListener 的子接口,还继承了 Ordered 接口。SmartApplicationListener 定义了两个 support 方法用于判断事件类型、来源类型是否和当前监听者匹配,这样监听者可以筛选自己感兴趣的事件和来源。继承 Ordered 接口后,该监听者具备了排序的功能,可以按照 order 从小到大的顺序给监听者确定一个优先级,从而确保执行顺序。

public interface Ordered {

    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    int getOrder();
}


public interface SmartApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {
    
    //指定支持哪些类型的事件
    //判断事件的类型是否和当前监听者匹配
    boolean supportsEventType(Class<? extends ApplicationEvent> eventType);

    //指定支持发生事件所在的类型
    //判断事件源类型是否和当前监听者匹配
    default boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }

    @Override
    default int getOrder() {
        return LOWEST_PRECEDENCE;
    }

}

GenericApplicationListener

GenericApplicationListener 接口是 ApplicationListener 的子接口,也继承了 Ordered 接口,同 SmartApplicationListener 一样具有事件筛选能力和排序能力。但筛选事件使用的是 ResolvableType 类型,而不是 ApplicationEvent 类型。

ResolvableType类型是Spring4之后添加进来的获取泛型信息的工具,通过ResolvableType可以获取到传入的泛型的各种信息。

public interface GenericApplicationListener extends ApplicationListener<ApplicationEvent>, Ordered {

    //指定支持哪些类型的事件
    //判断事件的类型是否和当前监听者匹配
    boolean supportsEventType(ResolvableType eventType);

    //指定支持发生事件所在的类型
    //判断事件源类型是否和当前监听者匹配
    default boolean supportsSourceType(@Nullable Class<?> sourceType) {
        return true;
    }

    @Override
    default int getOrder() {
        return LOWEST_PRECEDENCE;
    }

}

参考:
https://blog.csdn.net/zrudong/article/details/78567473

https://zhuanlan.zhihu.com/p/40071652

https://blog.csdn.net/chenwiehuang/article/details/80641811

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343