spring的监听器和事件机制

背景

之前其实都不知道这些东西, 后来还是前一段时间看Spring的refresh()函数的时候才知道这个, 后来做gateway网关的时候,也需要用到这个, 所以就来学习一下

事件机制是spring的重要功能之一。基于观察者模式。 
一句话概括用法就是一个地方发出个通知, 很多其他地方能收到通知并做出相应的动作。

示例demo

感觉还是先知道怎么用,然后这个东西做了什么之后才能够映像深刻
重要概念:
在事件机制中有几个概念:

  • 事件
  • 事件发布者(一个)
  • 事件监听者(多个)
public class MyEvent extends ApplicationEvent {
    private String msg;

    public MyEvent(String msg) {
        super(msg);
        this.msg = msg;
    }

    //自定义一个方法,这个方法也可以随意写,这里也是测试用
    public void doWork(){
        System.out.println("********My event**************");
        System.out.println(msg);
        System.out.println("*******************************");
    }

}
@Service
public class MyListener implements ApplicationListener<ApplicationEvent> {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof MyEvent) {
            MyEvent myEvent = (MyEvent) event;
            ((MyEvent) event).doWork();
        }
    }
}

@Component
public class TestListener {

    @EventListener
    public void Listen(MyEvent event) {
        event.doWork();
    }
}
public class PlatformApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(PlatformApplication.class);
        for (int i = 0; i < 10 ; i++) {
            MyEvent event = new MyEvent("hello" + i);
            ctx.publishEvent(event);
        }

        log.info("start success");
    }
}

然后就可以看出,发布事件后, 后面对应的监听者就会做出响应的事件

定义事件

  • 1 核心是继承ApplicationEvent
  • 2 ApplicationEvent作为父类,构造方法要求必须传入参数Object source,表示是谁发布的事件
  • 3 ApplicationEvent作为父类,是可序列化的,有serialVersionUID,建议子类也加上

发布事件

  • 1 核心是注入 ApplicationEventPublisher,并使用其中的publishEvent方法 发布事件

  • 2 也可以使用spring上下文容器ConfigurableApplicationContext,因为它也继承了ApplicationEventPublisher

  • 监听事件

  • 1核心是实现 ApplicationListener 接口

  • 2实现onApplicationEvent方法,方法内就是监听者对事件发生的响应

使用注解@EventListener

/**
 * 使用@EventListener实现监听者
 */
@Component
public class Listener3 {

    @EventListener
    public void listenerMyEvent1(MyEvent1 event) {
        System.out.println("Listener3 收到事件通知:" + event.getMessage());
        //do something
    }

}

该注解作用在方法上,跟前面普通监听者写法一样,方法参数列表里是要监听的事件类型。
在事件驱动编程中,监听者可能数量众多,有注解将会大大简化开发。 而且之前的写法一个类只能监听一个事件,有了注解,我们可以在一个类中写多个方法,监听多个事件,如下所示

/**
 * 学生监听多种事件,并作出响应
 */
@Component
public class Student {

    /**
     * 监听上课铃声
     * @param event1
     */
    @EventListener
    public void listenSchoolBell(MyEvent1 event1) {
        System.out.println("上课铃响了,我要去上课...");
        //do something
    }
    
    /**
     * 监听老师提问
     * @param event2
     */
    @EventListener
    public void listenTeacherAskMe(MyEvent1 event2) {
        System.out.println("老师提问了,我要起立回答问题...");
        //do something
    }
    
}

如果想要异步监听,那么可以使用@Async
为什么前面我们说“应用在可能出异常”的事件监听者方法上,而不是全都加上异步注解呢?因为资源占用,每使用一个异步方法,整个系统就多增加一个线程,而且在执行完之后并不会自动销毁,那些我们写的时候就确定不可能出现阻塞的监听器就可以不用加,当然 最好是使用线程池。

Spring监听器

一开始我们也讲了,实际生产中很少直接使用事件机制。而是间接的应用比较多,我们平时总能在web开发中听到监听器的说法(和过滤器还经常混淆),这里的监听器和前面的“监听者”是不是有关联呢,答案是肯定的。
打开定义事件的父类ApplicationEvent,使用IDE看一下该类的所有子类(涂掉的是我们自己定义的事件)

image.png

这里显示的都是Spring自带的事件,是我们可以直接进行监听的事件,看看有没有自己熟悉的类。下面以其中的相对比较常见的RequestHandleEvent为例,看看如何使用Spring提供的“监听器”。

RequestHandleEvent
功能:在SpringMvc收到请求时,会触发一个事件,我们可以通过监听该事件捕捉到用户每个发来的rququest请求,下面是个Demo,用来监听这种事件

/**
 * 监听RequestHandledEvent事件
 */
@Component
public class Listener5 {

    @EventListener
    public void listenerMyEvent1(RequestHandledEvent event) {
        
        System.out.println("Listener5 收到事件通知:" + event.getShortDescription());
        //do something
    }

}

当有一个请求过来的时候

---------test2--------
Listener5 收到事件通知:url=[/project/test2]; client=[0:0:0:0:0:0:0:1]; session=[null]; user=[null]; 

可见,spring给提供的事件,监听方式和我们自己写的一样(本质都是一样的)。这个监听器就实现了类似于“request过滤器”的效果。上面列举的其他spring内部事件用法也都是如此,一些常用的功能描述如下

  • RequestHandledEvent request请求事件
  • ContextClosedEvent 容器关闭事件
  • ContextStartedEvent 容器启动事件
  • ContextStoppedEvent 容器停止事件

观察者模式

像Spring这样的优秀开源软件都会应用很多设计模式及编程思想。事件机制就是一个很好的例子。“事件驱动编程”本身就是一个很火的概念,Spring的事件机制就是“事件驱动编程”思想的应用。而这种思想更抽象的说法叫“观察者模式”或者叫“监听者模式” /“发布-订阅模式”(和观察者模式有细微差别:发布者和订阅者都不需要知道对方的存在

目标主题(Subject,目标主题这个翻译是不太准确的)
实际开发中目标主题是搭建这个观察者模式主要代码逻辑,包括

  • 注册中心(增删改查观察者)
  • 事件发生时,遍历所有的观察者,并执行观察者实现的响应事件的方法

观察者(Observer:观察者模式的名字就来源于这里)

  • 根据自己的需要实现事件触发接口方法
  • 等待事件发生后被动被调用

代码示例

/**
 * 目标主题(观察模式的运作中心)
 */
public interface Subject {
    /**
     * 增加观察者
     * @param observer
     */
    void addObserver(Observer observer);
    
    /**
     * 删除观察者
     * @param observer
     */
    void removeObserver(Observer observer);
    
    /**
     * 通知观察者
     */
    void inform();
}

定义观察者Observer

/**
 * 观察者
 */
public interface Observer {
    /**
     * 观察者在事件发生时的响应
     */
    void todo();
}

那目标主题是怎么通知到每个观察者的呢。这里并没有什么高深的技术,就是把遍历目标主题里注册的全部观察者,调用他们的todo()方法。也就是说,所谓观察者在模式中是“被动观察”的,下面是目标主题的实现类

public class SubjectImpl implements Subject {
    
    private List<Observer> observerList = new ArrayList<Observer>();

    @Override
    public void addObserver(Observer observer) {
        observerList.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observerList.remove(observer);
    }

    @Override
    public void inform() {
        System.out.println("事件触发...");
        for (Observer observer : observerList) {
            observer.todo();
        }
    }

}


被观察者是知道自己的观察者有哪些的

小结

  • Observer对应Listener,这个比较好理解。
  • 事件机制中的Event是spring新增的抽象概念(更准确的说是“事件驱动编程”里抽象出来的概念)。
  • 事件机制中的“发布者”其实只是一个事件触发点,并不在观察者模式的定义里。
  • 从Subject所包含的核心逻辑就验证里前面的猜想:监听者者是一个一个顺序执行的。而观察者模式里的核心Subject是spring给我实现好的。下面我们通过源码,看一下Spring给实现好的Subject。

源码解析

事件广播器

前面说的EventMulticaster(事件广播器)在spring中实际叫ApplicationEventMulticaster ,下面是它的接口方法(之所先看接口,是因为接口是spring的骨架,定义了spring的原始设计想法,后面的抽象方法、具体实现都比较复杂,但都只是对接口的填充而已)

image.png

是不是和我们定义的Subject很像,虽然这里有7个方法,但归结起来还是那两个需求(3个方法):

  • 增删维护观察者: addxxx, removexxx
  • 通知观察者: multicastEvent(对应上面我们写的todo()方法)

看一下这个接口的注释说明

Interface to be implemented by objects that can manage a number of ApplicationListener objects and publish events to them.
这是一个这样的接口:它用来管理一堆监听者对象并发布事件给他们

Spring事件机制流程

通过前面介绍事件广播器的几个功能,大致可以猜到Spring为我们做了些什么,但还是对细节会有一些疑问:

  • Listener是什么时候加载的,谁加载的
  • 是怎么执行到Listener里的方法里的
    我就不直接截源码的图里,因为以本人看文章的体验来说,枯燥的截图对读者帮助并不大,下面直接看根据源码画的流程图


    image.png

流程解释

1流程分为两个阶段

  • 一个是启动spring容器
  • 一个是我们触发事件的时候

2核心还是事件广播器, ApplicationEventMulticaster (这里实际指的是它的实现类ApplicationEventMulticaster,SimpleApplicationEventMulticaster)

3 增加监听器的时候在启动spring容器的时候完成(图中紫红色的部分)。 这也是spring容器的核心位置。 为防止读者在自己看源码的时候疑惑,图中我特意把两个加载linstener的过程都画出来。这两个addxxx分别是:

  • 1增加普通的监听器
  • 2增加使用注解(@EventListener)实现的监听器

4 事件发布。 这是我们写程序可触及到的一部分。 核心是ApplicationEventPublisher. 这里会首先去调用事件广播器的getApplicationListener方法, 拿到所有的监听器(由于前面启动的时候已经加载所有的监听器, 所以这边可以拿到), 然后逐个调用监听器里面的方法。

参考博客

https://blog.csdn.net/yunduanyou/article/details/107123434

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