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