事件監聽(基於SpringBoot示例)

在實際開發中,業務代碼與輔助代碼的解耦是一個熱點話題,如:通過AOP記錄入參出參、使用事件監聽記錄錯誤信息等是一個不錯的選擇。

概述

        事件的發佈與監聽從屬於觀察者模式;和MQ相比,事件的發佈與監聽偏向於處理“體系內”的某些邏輯。事件的發佈與監聽總體分爲以下幾個步驟:

步驟 相關事宜
1 定義事件
2 定義(用於處理某種事件的)監聽器
3 註冊監聽器
4 發佈事件(,監聽到了該事件的監聽器自動進行相關邏輯處理)

詳細(示例)說明

第一步通過繼承ApplicationEvent來自定義事件。

import org.springframework.context.ApplicationEvent;

/**
 * 自定義事件
 *
 * 注:繼承ApplicationEvent即可。
 *
 * @author JustryDeng
 * @date 2019/11/19 6:36
 */
public class MyEvent extends ApplicationEvent {


    /**
     * 構造器
     *
     * @param source
     *            該事件的相關數據
     *
     * @date 2019/11/19 6:40
     */
    public MyEvent(Object source) {
        super(source);
    }
}

注:構造器的參數爲該事件的相關數據對象,監聽器可以獲取到該數據對象,進而進行相關邏輯處理。

第二步自定義監聽器。

  • 方式一(推薦) 通過實現ApplicationListener來自定義監聽器,其中E爲此監聽器要監聽的事件。
    /**
     * 自定義監聽器
     *
     * 注:實現ApplicationListener<E extends ApplicationEvent>即可,
     *    其中E爲此監聽器要監聽的事件。
     *
     * @author JustryDeng
     * @date 2019/11/19 6:44
     */
    public class MyListenerOne implements ApplicationListener<MyEvent> {
    
        /**
         * 編寫處理事件的邏輯
         *
         * @param event
         *            當前事件對象
         */
        @Override
        public void onApplicationEvent(MyEvent event) {
            /// 當前事件對象攜帶的數據
            /// Object source = event.getSource();
            System.out.println(
                    "線程-【" + Thread.currentThread().getName() + "】 => "
                    + "監聽器-【MyListenerOne】 => "
                    + "監聽到的事件-【" + event + "】"
            );
    
        }
    }
    
    注:需要重寫onApplicationEvent方法來自定義相關事件的處理邏輯。
  • 方式二 在某個方法上使用@EventListener註解即可。
    import org.springframework.context.event.EventListener;
    
    /**
     * 自定義監聽器
     *
     * 注:在某個方法上使用@EventListener註解即可。
     *    追注一: 這個方法必須滿足: 最多能有一個參數。
     *    追注二:    若只是監聽一種事件,那麼這個方法的參數類型應爲該事
     *           件對象類;P.S.:該事件的子類事件,也屬於該事件,也會被監聽到。
     *              若要監聽多種事件,那麼可以通過@EventListener註解
     *           的classes屬性指定多個事件,且保證這個方法無參;
     *
     *
     *
     * @author JustryDeng
     * @date 2019/11/19 6:44
     */
    public class MyListenerTwo {
    
        /**
         * 編寫處理事件的邏輯
         *
         * @param event
         *            當前事件對象
         */
        @EventListener
        public void abc(MyEvent event) {
            /// 當前事件對象攜帶的數據
            /// Object source = event.getSource();
            System.out.println(
                    "線程-【" + Thread.currentThread().getName() + "】 => "
                            + "監聽器-【MyListenerTwo】 => "
                            + "監聽到的事件-【" + event + "】"
            );
        }
    
    }
    
    注:在某個方法上使用@EventListener註解即可。
    注:被@EventListener註解方法必須滿足: 最多能有一個參數。
    注:若只是監聽一種事件,那麼這個方法的參數類型應爲該事件對象類;
           P.S.:該事件的子類事件,也屬於該事件,也會被監聽到。
    注:若要監聽多種事件,那麼可以通過@EventListener註解的classes屬性指定多個事件,
           且保證這個方法無參。
    提示:還可通過設置@EventListener註解的condition屬性來對事件進行選擇性處理(P.S.:當然用代碼也能做到)。

第三步註冊監聽器。

        所謂註冊監聽器,其實質就是將監聽器進行IOC處理,讓容器管理監聽器的生命週期。 在SpringBoot中,有以下方式可以達到這些效果:

方式 具體操作 適用範圍 能否搭配@Async註解,進行異步監聽 優先級(即:當這三種方式註冊的(監聽同一類型事件的)監聽器都同時存在時,那麼一個事件發佈後,哪一種方式先監聽到)
在SpringBoot啓動類的main方法中,使用SpringApplication的addListeners方法進行註冊
提示:當在進行單元測試時,(由於不會走SpringBoot啓動的類main方法,所以)此方式不生效。
實現了ApplicationListener的監聽器 不能 取決於Bean被Ioc的先後順序。可通過設置@Order優先級的方式,來達到調整監聽器優先級的目的。
提示:實際開發中,考慮優先級的意義不大。
(推薦) 通過@Component或類似註解,將監聽器(或監聽器方法所在的)類IOC 實現了ApplicationListener的監聽器以及通過@EventListener註解指定的監聽器
在SpringBoot配置文件(.properties文件或.yml文件)中指定監聽器(或監聽器方法所在的)類
注:多個監聽器,使用逗號分割即可。
實現了ApplicationListener的監聽器 不能
  • 方式①示例
    在這裏插入圖片描述

  • 方式②示例
    在這裏插入圖片描述
    或者

    在這裏插入圖片描述

  • 方式③示例
    在這裏插入圖片描述
    注:爲了美觀,建議換行(在properties文件中使用\換行):

    在這裏插入圖片描述

第四步發佈事件,觸發監聽。

        使用實現了ApplicationEventPublisher接口的類(常用ApplicationContext)的publishEvent(ApplicationEvent event)方法發佈事件。
在這裏插入圖片描述
注:SpringBoot啓動時,返回的ConfigurableApplicationContext是ApplicationContext的子類,所以如果想在SpringBoot啓動後就立馬發佈事件的話,可以這樣寫:
在這裏插入圖片描述

驗證測試

  • 按上述示例監聽邏輯,編寫示例:
    在這裏插入圖片描述
  • 運行main方法,啓動SpringBoot:
    在這裏插入圖片描述
  • 控制檯輸出:
    在這裏插入圖片描述
    SpringBoot中事件的發佈與監聽初步學習完畢!

同步監聽與異步監聽

同步監聽

        按上文中的配置,實際上默認是同步監聽機制。所謂同步監聽,即:業務邏輯與監聽器的邏輯在同一個線程上、按順序執行

  • 舉例說明一:
            假設某線程α,線程β都有發佈各自的事件,那麼α線程發佈的事件會被α線程進行監聽器邏輯處理,β線程發佈的事件會被β線程進行監聽器邏輯處理。

  • 舉例說明二
            假設某線程β要做的總體邏輯流程是,做A => 發事件x => 做B => 發事件y => 發事件z => 返回響應,那麼同步監聽下是這樣的: 線程β先做A,(A做完後)接着做事件x對應的監聽器邏輯,(x的監聽器邏輯做完後,線程β才能)接着做B,(B做完後)接着做事件y對應的監聽器邏輯,(y的監聽器邏輯做完後,線程β才能)接着做事件z對應的監聽器邏輯,最後才能返回響應。

異步監聽

        如果需要異步監聽,那麼需要開啓異步功能(見下文示例),所謂異步監聽即:業務邏輯與監聽器的邏輯不在同一個線程上,處理監聽器邏輯的事會被線程池中的某些線程異步併發着做

  • 舉例說明
            假設某線程β要做的總體邏輯流程是,做A => 發事件x => 做B => 發事件y => 發事件z => 返回響應,那麼異步監聽下是這樣的:線程β先做A,(A做完後)發佈事件x,(線程β不管x的監聽器邏輯)緊接着做B,(B做完後,線程β)接着發佈事件y,(線程β不管y的監聽器邏輯)緊接着發佈事件z,(線程β不管z的監聽器邏輯)緊接着直接返回響應。而線程β發佈的事件對應的各個監聽器邏輯,會由線程池中的某些線程異步併發着做

開啓異步功能

  1. 第一步:@EnableAsync啓用異步功能。
    在這裏插入圖片描述
  2. 第二步:@Async指定異步方法。
    在這裏插入圖片描述
  3. 第三步(可選): 自定義配置線程池執行器Executor(提示:配置Executor後,在使用@Async註解時,可以通過設置其屬性來指定使用哪一個Executor)。
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ThreadPoolExecutor;
    
    /**
     * 自定義線程池Executor。
     *
     * 注: 我們常用的ExecutorService就繼承自Executor。
     *
     * 注:關於線程池的各個參數的介紹、各個參數的關係,可詳見<linked>https://blog.csdn.net/justry_deng/article/details/89331199</linked>
     *
     * @author JustryDeng
     * @date 2019/11/25 11:05
     */
    @Configuration
    public class SyncExecutor {
    
        /** 核心線程數 */
        private static final int CORE_POOL_SIZE = 5;
    
        /** 最大線程數 */
        private static final int MAX_POOL_SIZE = 100;
    
        /** 阻塞隊列容量 */
        private static final int QUEUE_CAPACITY = 20;
    
        @Bean
        public Executor myAsyncExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(CORE_POOL_SIZE);
            executor.setMaxPoolSize(MAX_POOL_SIZE);
            executor.setQueueCapacity(QUEUE_CAPACITY);
            executor.setThreadNamePrefix("JustryDeng-Executor-");
            // 設置,當任務滿額時將新任務(如果有的話),打回到原線程去執行。
            executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
            executor.initialize();
            return executor;
        }
    
    }
    

注:@Async指定異步方法時,就可以選擇使用哪一個線程池Executor了,如:
在這裏插入圖片描述

同步監聽與異步監聽的比較

在這裏插入圖片描述


SpringBoot中事件的發佈與監聽學習完畢!


^_^ 如有不當之處,歡迎指正

^_^ 測試代碼託管鏈接
         https://github.com/JustryDeng…Abc_EventListener_Demo

^_^ 本文已經被收錄進《程序員成長筆記(六)》,筆者JustryDeng

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章