spring-event-事件監聽機制實現

primaryC發表於2024-03-08

1. 事件監聽機制概述

1. 場景

模型版本更新了,新版本需要繼承老版本的構件分享、自定義屬性、著色資料,以後還可能有其他資料要繼承,這些資料之間沒有直接聯絡,就是當模型版本變更的時候,他們各自需要執行。

2. 涉及的三個物件

  • 事件源(提供事件處理時的後設資料)

這裡就是模型版本更新了

  • 監聽器(事件處理者)

構件分享繼承是一個監聽者,監聽到版本更新的事件,需要自己處理點東西
自定義屬性繼承也是一個監聽者。。
著色資料繼承也是一個監聽者。。。

  • 事件釋出者(呼叫者)

2. 示例

1. 事件源 ApplicationEvent

定義一個事件源,只需要繼承 org.springframework.context.ApplicationEvent.ApplicationEvent 或其子類

package com.demo.event;

import org.springframework.context.ApplicationEvent;

/**
 * 事件源:模型變更了
 * 虛擬碼:模型變更
 * */
public class ModelExchangeEvent extends ApplicationEvent {

    private Integer modelId;
    private Integer modelVersion;

    /**
     * Create a new {@code ApplicationEvent}.
     *
     * @param source the object on which the event initially occurred or with
     *               which the event is associated (never {@code null})
     */
    public ModelExchangeEvent(Object source, Integer modelId, Integer modelVersion) {
        super(source);
        this.modelId = modelId;
        this.modelVersion = modelVersion;
    }

    public Integer getModelId() {
        return modelId;
    }
    public Integer getModelVersion() {
        return modelVersion;
    }
    public void setModelId(Integer modelId) {
        this.modelId = modelId;
    }
    public void setModelVersion(Integer modelVersion) {
        this.modelVersion = modelVersion;
    }
}

2. 監聽者 ApplicationListener

監聽器可以有多個,每個監聽器代表一種操作。

  • 透過繼承 ApplicationListener 實現監聽器
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * 虛擬碼:著色繼承
 * 實現 `ApplicationListener` 方式
 * */
@Component
@Slf4j
public class ModelExchangeColorListener implements ApplicationListener<ModelExchangeEvent> {
    @Override
    public void onApplicationEvent(ModelExchangeEvent e) {
        log.info("收到模型{}版本{}的變更通知!著色繼承開始!", e.getModelId(), e.getModelVersion());
        //模擬邏輯執行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("著色繼承結束!");
    }
}
  • 透過 @EventListener 註解實現監聽器
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 虛擬碼:繼承
 * 使用 @EventListener 方式
 * */
@Component
@Slf4j
public class ModelExchangePropertiesListener {

    /**
     * 屬性繼承
     * */
    @EventListener(ModelExchangeEvent.class)
    public void onEvent(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的變更通知!自定義屬性繼承開始!", e.getModelId(), e.getModelVersion());
        //模擬邏輯執行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("自定義屬性繼承結束!");
    }

    /**
     * 分享繼承
     * */
    @EventListener(ModelExchangeEvent.class)
    public void sss(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的變更通知!分享繼承開始!", e.getModelId(), e.getModelVersion());
        //模擬邏輯執行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("分享繼承結束!");
    }
}

3. 事件釋出者 ApplicationEventPublisher

實現了 ApplicationEventPublisher 介面,就是一個釋出者,透過 publishEvent 方法釋出事件。

  • 直接注入 ApplicationEventPublisher 物件
package com.demo.controller;

import com.demo.event.ModelExchangeEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/model")
@Slf4j
public class ModelController{

    /**
     * 使用 ApplicationEventPublisher 釋出事件
     * */
    @Autowired
    private ApplicationEventPublisher ap;

    @RequestMapping("/exchange")
    public void exchange(){
        log.info("虛擬碼:模型變更的邏輯");
        ap.publishEvent(new ModelExchangeEvent(ap, 9001, 2));
    }
}

這裡透過自動注入一個 ApplicationEventPublisher 來發布事件,這個注入的 publisher 其實就是啟動的 Spring 容器物件 ConfigurableApplicationContext run = SpringApplication.run(EventMain.class, args),也就是這個 run

  • 透過 ApplicationEventPublisherAware 來手動設定 publisher
package com.demo.controller;

import com.demo.event.ModelExchangeEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/model")
@Slf4j
public class ModelController implements ApplicationEventPublisherAware{

    /**
     * 使用 ApplicationEventPublisher 釋出事件
     * */
    private ApplicationEventPublisher ap;

    @RequestMapping("/exchange")
    public void exchange(){
        log.info("虛擬碼:模型變更的邏輯");
        this.ap.publishEvent(new ModelExchangeEvent(ap, 9001, 2));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.ap = applicationEventPublisher;
    }
}

跟上面差距不大

3. 重要元件說明

1. ApplicationEvent

Spring中所有事件的基類,繼承自java.util.EventObject。開發人員可以透過繼承ApplicationEvent類來建立自定義事件,在事件物件中封裝相關資訊。事件可以同步或非同步觸發,並支援條件事件和層次事件等特性。

2. ApplicationListener

是一個監聽器介面,用於處理特定型別的事件。當被感興趣的事件發生時,容器會呼叫相應的監聽器方法,傳入該事件作為引數。開發人員可以實現ApplicationListener介面來建立自己的監聽器,然後使用@EventListener註解或者配置檔案的方式進行註冊。

3. ApplicationEventMulticaster

是一個事件廣播器,將事件傳送給所有已註冊的ApplicationListener。預設情況下,Spring使用SimpleApplicationEventMulticaster實現事件廣播,但也可以透過配置使用其他實現方式,例如AsyncApplicationEventMulticaster和SimpleThreadScopeEventMulticaster。
就是透過這玩意來呼叫每個 listener 的處理邏輯。

4. ApplicationEventPublisher

釋出者,透過這個元件來發布事件。

4. 多個監聽者執行順序

預設情況下多個監聽者同步執行,使用 @Order 註解標註執行順序。

5. 非同步呼叫

也有兩種方式。

1. ApplicationEventMulticaster

package com.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;
import org.springframework.stereotype.Component;

import java.util.concurrent.Executor;

/**
 * 配置監聽器非同步執行
 * */
@Configuration
public class ListenerConfig {

    @Bean
    public ApplicationEventMulticaster applicationEventMulticaster() { //@1
        //建立一個事件廣播器
        SimpleApplicationEventMulticaster result = new SimpleApplicationEventMulticaster();
        //給廣播器提供一個執行緒池,透過這個執行緒池來呼叫事件監聽器
        Executor executor = this.applicationEventMulticasterThreadPool().getObject();
        //設定非同步執行器
        result.setTaskExecutor(executor);//@1
        return result;
    }

    //提供一個執行緒池
    @Bean
    public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool() {
        ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
        result.setThreadNamePrefix("modelThread-");
        result.setCorePoolSize(2);
        return result;
    }
}

  • 原理

  自定義 ApplicationEventMulticaster,並設定其執行緒池,監聽器最終是透過ApplicationEventMulticaster內部的實現來呼叫的,所以我們關注的重點就是這個類,這個類預設有個實現類 SimpleApplicationEventMulticaster,這個類是支援監聽器非同步呼叫的,內部有個執行緒池欄位:

private Executor taskExecutor;

SimpleApplicationEventMulticaster 執行邏輯:

@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) { 
            //執行緒池物件不為 null,就提交自定義監聽事件到執行緒池中
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            //執行緒池物件為 null,就順序執行每個 listener 自定義監聽事件
            invokeListener(listener, event);
        }
    }
}

所以我們需要定義一個 SimpleApplicationEventMulticaster 的 bean 並設定其執行緒池.

  • 弊端:這樣會使所有的監聽器都非同步執行

2. @Async

@EnableAsync 標註在啟動類上,來標識支援非同步。@Async 標註在指定 listener 上。這種應該就是傳說中 bean 的非同步執行。

package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 虛擬碼:著色繼承
 * 實現 ApplicationListener 方式
 * */
@Component
@Slf4j
@Async
public class ModelExchangeColorListener implements ApplicationListener<ModelExchangeEvent> {
    @Override
    public void onApplicationEvent(ModelExchangeEvent e) {
        log.info("收到模型{}版本{}的變更通知!著色繼承開始!", e.getModelId(), e.getModelVersion());
        //模擬邏輯執行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("著色繼承結束!");
    }
}
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 虛擬碼:繼承
 * 使用 @EventListener 方式
 * */
@Component
@Slf4j
public class ModelExchangePropertiesListener {

    /**
     * 屬性繼承
     * */
    @EventListener(ModelExchangeEvent.class)
    @Async
    public void onEvent(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的變更通知!自定義屬性繼承開始!", e.getModelId(), e.getModelVersion());
        //模擬邏輯執行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("自定義屬性繼承結束!");
    }

    /**
     * 分享繼承
     * */
    @EventListener(ModelExchangeEvent.class)
    @Async
    public void sss(ModelExchangeEvent e){
        log.info("收到模型{}版本{}的變更通知!分享繼承開始!", e.getModelId(), e.getModelVersion());
        //模擬邏輯執行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("分享繼承結束!");
    }
}

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class EventMain {
    public static void main(String[] args) {
        SpringApplication.run(EventMain.class, args);
    }
}

  • 預設的執行緒池 SimpleAsyncTaskExecutor 不太行,可以透過 @Async("applicationEventMulticasterThreadPool") 自己換一個。
package com.demo.event;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * 虛擬碼:著色繼承
 * 實現 ApplicationListener 方式
 * */
@Component
@Slf4j
@Async("applicationEventMulticasterThreadPool")
public class ModelExchangeColorListener implements ApplicationListener<ModelExchangeEvent> {
    @Override
    public void onApplicationEvent(ModelExchangeEvent e) {
        log.info("收到模型{}版本{}的變更通知!著色繼承開始!", e.getModelId(), e.getModelVersion());
        //模擬邏輯執行
        try {
            Thread.sleep(2_000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        log.info("著色繼承結束!");
    }
}

自定義的執行緒池

package com.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.scheduling.concurrent.ThreadPoolExecutorFactoryBean;

import java.util.concurrent.Executor;

/**
 * 配置監聽器非同步執行
 * */
@Configuration
public class ListenerConfig {
    //提供一個執行緒池
    @Bean
    public ThreadPoolExecutorFactoryBean applicationEventMulticasterThreadPool() {
        ThreadPoolExecutorFactoryBean result = new ThreadPoolExecutorFactoryBean();
        result.setThreadNamePrefix("modelThread-");
        result.setCorePoolSize(2);
        return result;
    }
}

參考文獻

https://blog.csdn.net/lin1214000999/article/details/124707979
原理參考:https://cloud.tencent.com/developer/article/2364790

相關文章