spring的監(jiān)聽器和事件機(jī)制

背景

之前其實(shí)都不知道這些東西, 后來還是前一段時(shí)間看Spring的refresh()函數(shù)的時(shí)候才知道這個(gè), 后來做gateway網(wǎng)關(guān)的時(shí)候,也需要用到這個(gè), 所以就來學(xué)習(xí)一下

事件機(jī)制是spring的重要功能之一?;谟^察者模式?!?br> 一句話概括用法就是一個(gè)地方發(fā)出個(gè)通知, 很多其他地方能收到通知并做出相應(yīng)的動(dòng)作。

示例demo

感覺還是先知道怎么用,然后這個(gè)東西做了什么之后才能夠映像深刻
重要概念:
在事件機(jī)制中有幾個(gè)概念:

  • 事件
  • 事件發(fā)布者(一個(gè))
  • 事件監(jiān)聽者(多個(gè))
public class MyEvent extends ApplicationEvent {
    private String msg;

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

    //自定義一個(gè)方法,這個(gè)方法也可以隨意寫,這里也是測試用
    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");
    }
}

然后就可以看出,發(fā)布事件后, 后面對(duì)應(yīng)的監(jiān)聽者就會(huì)做出響應(yīng)的事件

定義事件

  • 1 核心是繼承ApplicationEvent
  • 2 ApplicationEvent作為父類,構(gòu)造方法要求必須傳入?yún)?shù)Object source,表示是誰發(fā)布的事件
  • 3 ApplicationEvent作為父類,是可序列化的,有serialVersionUID,建議子類也加上

發(fā)布事件

  • 1 核心是注入 ApplicationEventPublisher,并使用其中的publishEvent方法 發(fā)布事件

  • 2 也可以使用spring上下文容器ConfigurableApplicationContext,因?yàn)樗怖^承了ApplicationEventPublisher

  • 監(jiān)聽事件

  • 1核心是實(shí)現(xiàn) ApplicationListener 接口

  • 2實(shí)現(xiàn)onApplicationEvent方法,方法內(nèi)就是監(jiān)聽者對(duì)事件發(fā)生的響應(yīng)

使用注解@EventListener

/**
 * 使用@EventListener實(shí)現(xiàn)監(jiān)聽者
 */
@Component
public class Listener3 {

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

}

該注解作用在方法上,跟前面普通監(jiān)聽者寫法一樣,方法參數(shù)列表里是要監(jiān)聽的事件類型。
在事件驅(qū)動(dòng)編程中,監(jiān)聽者可能數(shù)量眾多,有注解將會(huì)大大簡化開發(fā)。 而且之前的寫法一個(gè)類只能監(jiān)聽一個(gè)事件,有了注解,我們可以在一個(gè)類中寫多個(gè)方法,監(jiān)聽多個(gè)事件,如下所示

/**
 * 學(xué)生監(jiān)聽多種事件,并作出響應(yīng)
 */
@Component
public class Student {

    /**
     * 監(jiān)聽上課鈴聲
     * @param event1
     */
    @EventListener
    public void listenSchoolBell(MyEvent1 event1) {
        System.out.println("上課鈴響了,我要去上課...");
        //do something
    }
    
    /**
     * 監(jiān)聽老師提問
     * @param event2
     */
    @EventListener
    public void listenTeacherAskMe(MyEvent1 event2) {
        System.out.println("老師提問了,我要起立回答問題...");
        //do something
    }
    
}

如果想要異步監(jiān)聽,那么可以使用@Async
為什么前面我們說“應(yīng)用在可能出異?!钡氖录O(jiān)聽者方法上,而不是全都加上異步注解呢?因?yàn)橘Y源占用,每使用一個(gè)異步方法,整個(gè)系統(tǒng)就多增加一個(gè)線程,而且在執(zhí)行完之后并不會(huì)自動(dòng)銷毀,那些我們寫的時(shí)候就確定不可能出現(xiàn)阻塞的監(jiān)聽器就可以不用加,當(dāng)然 最好是使用線程池。

Spring監(jiān)聽器

一開始我們也講了,實(shí)際生產(chǎn)中很少直接使用事件機(jī)制。而是間接的應(yīng)用比較多,我們平時(shí)總能在web開發(fā)中聽到監(jiān)聽器的說法(和過濾器還經(jīng)?;煜?,這里的監(jiān)聽器和前面的“監(jiān)聽者”是不是有關(guān)聯(lián)呢,答案是肯定的。
打開定義事件的父類ApplicationEvent,使用IDE看一下該類的所有子類(涂掉的是我們自己定義的事件)

image.png

這里顯示的都是Spring自帶的事件,是我們可以直接進(jìn)行監(jiān)聽的事件,看看有沒有自己熟悉的類。下面以其中的相對(duì)比較常見的RequestHandleEvent為例,看看如何使用Spring提供的“監(jiān)聽器”。

RequestHandleEvent
功能:在SpringMvc收到請(qǐng)求時(shí),會(huì)觸發(fā)一個(gè)事件,我們可以通過監(jiān)聽該事件捕捉到用戶每個(gè)發(fā)來的rququest請(qǐng)求,下面是個(gè)Demo,用來監(jiān)聽這種事件

/**
 * 監(jiān)聽RequestHandledEvent事件
 */
@Component
public class Listener5 {

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

}

當(dāng)有一個(gè)請(qǐng)求過來的時(shí)候

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

可見,spring給提供的事件,監(jiān)聽方式和我們自己寫的一樣(本質(zhì)都是一樣的)。這個(gè)監(jiān)聽器就實(shí)現(xiàn)了類似于“request過濾器”的效果。上面列舉的其他spring內(nèi)部事件用法也都是如此,一些常用的功能描述如下

  • RequestHandledEvent request請(qǐng)求事件
  • ContextClosedEvent 容器關(guān)閉事件
  • ContextStartedEvent 容器啟動(dòng)事件
  • ContextStoppedEvent 容器停止事件

觀察者模式

像Spring這樣的優(yōu)秀開源軟件都會(huì)應(yīng)用很多設(shè)計(jì)模式及編程思想。事件機(jī)制就是一個(gè)很好的例子?!?strong>事件驅(qū)動(dòng)編程”本身就是一個(gè)很火的概念,Spring的事件機(jī)制就是“事件驅(qū)動(dòng)編程”思想的應(yīng)用。而這種思想更抽象的說法叫“觀察者模式”或者叫“監(jiān)聽者模式” /“發(fā)布-訂閱模式”(和觀察者模式有細(xì)微差別:發(fā)布者和訂閱者都不需要知道對(duì)方的存在

目標(biāo)主題(Subject,目標(biāo)主題這個(gè)翻譯是不太準(zhǔn)確的)
實(shí)際開發(fā)中目標(biāo)主題是搭建這個(gè)觀察者模式主要代碼邏輯,包括

  • 注冊(cè)中心(增刪改查觀察者)
  • 事件發(fā)生時(shí),遍歷所有的觀察者,并執(zhí)行觀察者實(shí)現(xiàn)的響應(yīng)事件的方法

觀察者(Observer:觀察者模式的名字就來源于這里)

  • 根據(jù)自己的需要實(shí)現(xiàn)事件觸發(fā)接口方法
  • 等待事件發(fā)生后被動(dòng)被調(diào)用

代碼示例

/**
 * 目標(biāo)主題(觀察模式的運(yùn)作中心)
 */
public interface Subject {
    /**
     * 增加觀察者
     * @param observer
     */
    void addObserver(Observer observer);
    
    /**
     * 刪除觀察者
     * @param observer
     */
    void removeObserver(Observer observer);
    
    /**
     * 通知觀察者
     */
    void inform();
}

定義觀察者Observer

/**
 * 觀察者
 */
public interface Observer {
    /**
     * 觀察者在事件發(fā)生時(shí)的響應(yīng)
     */
    void todo();
}

那目標(biāo)主題是怎么通知到每個(gè)觀察者的呢。這里并沒有什么高深的技術(shù),就是把遍歷目標(biāo)主題里注冊(cè)的全部觀察者,調(diào)用他們的todo()方法。也就是說,所謂觀察者在模式中是“被動(dòng)觀察”的,下面是目標(biāo)主題的實(shí)現(xiàn)類

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("事件觸發(fā)...");
        for (Observer observer : observerList) {
            observer.todo();
        }
    }

}


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

小結(jié)

  • Observer對(duì)應(yīng)Listener,這個(gè)比較好理解。
  • 事件機(jī)制中的Event是spring新增的抽象概念(更準(zhǔn)確的說是“事件驅(qū)動(dòng)編程”里抽象出來的概念)。
  • 事件機(jī)制中的“發(fā)布者”其實(shí)只是一個(gè)事件觸發(fā)點(diǎn),并不在觀察者模式的定義里。
  • 從Subject所包含的核心邏輯就驗(yàn)證里前面的猜想:監(jiān)聽者者是一個(gè)一個(gè)順序執(zhí)行的。而觀察者模式里的核心Subject是spring給我實(shí)現(xiàn)好的。下面我們通過源碼,看一下Spring給實(shí)現(xiàn)好的Subject。

源碼解析

事件廣播器

前面說的EventMulticaster(事件廣播器)在spring中實(shí)際叫ApplicationEventMulticaster ,下面是它的接口方法(之所先看接口,是因?yàn)榻涌谑莝pring的骨架,定義了spring的原始設(shè)計(jì)想法,后面的抽象方法、具體實(shí)現(xiàn)都比較復(fù)雜,但都只是對(duì)接口的填充而已)

image.png

是不是和我們定義的Subject很像,雖然這里有7個(gè)方法,但歸結(jié)起來還是那兩個(gè)需求(3個(gè)方法):

  • 增刪維護(hù)觀察者: addxxx, removexxx
  • 通知觀察者: multicastEvent(對(duì)應(yīng)上面我們寫的todo()方法)

看一下這個(gè)接口的注釋說明

Interface to be implemented by objects that can manage a number of ApplicationListener objects and publish events to them.
這是一個(gè)這樣的接口:它用來管理一堆監(jiān)聽者對(duì)象并發(fā)布事件給他們

Spring事件機(jī)制流程

通過前面介紹事件廣播器的幾個(gè)功能,大致可以猜到Spring為我們做了些什么,但還是對(duì)細(xì)節(jié)會(huì)有一些疑問:

  • Listener是什么時(shí)候加載的,誰加載的
  • 是怎么執(zhí)行到Listener里的方法里的
    我就不直接截源碼的圖里,因?yàn)橐员救丝次恼碌捏w驗(yàn)來說,枯燥的截圖對(duì)讀者幫助并不大,下面直接看根據(jù)源碼畫的流程圖


    image.png

流程解釋

1流程分為兩個(gè)階段

  • 一個(gè)是啟動(dòng)spring容器
  • 一個(gè)是我們觸發(fā)事件的時(shí)候

2核心還是事件廣播器, ApplicationEventMulticaster (這里實(shí)際指的是它的實(shí)現(xiàn)類ApplicationEventMulticaster,SimpleApplicationEventMulticaster)

3 增加監(jiān)聽器的時(shí)候在啟動(dòng)spring容器的時(shí)候完成(圖中紫紅色的部分)?!∵@也是spring容器的核心位置?!榉乐棺x者在自己看源碼的時(shí)候疑惑,圖中我特意把兩個(gè)加載linstener的過程都畫出來。這兩個(gè)addxxx分別是:

  • 1增加普通的監(jiān)聽器
  • 2增加使用注解(@EventListener)實(shí)現(xiàn)的監(jiān)聽器

4 事件發(fā)布?!∵@是我們寫程序可觸及到的一部分?!『诵氖茿pplicationEventPublisher. 這里會(huì)首先去調(diào)用事件廣播器的getApplicationListener方法, 拿到所有的監(jiān)聽器(由于前面啟動(dòng)的時(shí)候已經(jīng)加載所有的監(jiān)聽器, 所以這邊可以拿到), 然后逐個(gè)調(diào)用監(jiān)聽器里面的方法。

參考博客

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容