Spring事件如何異步執行?
環境:SpringBoot2.7.16
1. 簡介
ApplicationContext 中的事件處理是通過 ApplicationEvent 類和 ApplicationListener 接口提供的。如果在上下文中部署了實現 ApplicationListener 接口的 Bean,那么每次 ApplicationEvent 發布到 ApplicationContext 時,都會通知該 Bean。從本質上講,這就是標準的觀察者設計模式。
從Spring 4.2開始,事件基礎設施得到了顯著改進,并提供了基于注釋的模型以及發布任意事件的能力(也就是說,不一定是從ApplicationEvent擴展的對象)。當這樣的對象被發布時,我們將它包裝在一個事件中。
以下是一個簡單的事件應用
1.1 定義事件對象
public class PackEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
public PackEvent(Object source) {
super(source);
}
}
1.2 定義事件監聽
@Component
public class PackEventListener implements ApplicationListener<PackEvent> {
@Override
public void onApplicationEvent(PackEvent event) {
System.out.println("觸發事件...") ;
}
}
1.3 發布事件
@Resource
private ApplicationEventMulticaster eventMulticaster ;
public void run(ApplicationArguments args) throws Exception {
eventMulticaster.multicastEvent(new PackEvent("自定義Pack")) ;
}
以上Spring事件系統的完整應用實例。在默認情況下該種事件處理方式是同步的,也就是事件的發布者與事件的處理都是同一個線程中,那這就要求我們的事件處理程序不應該處理復雜耗時的任務,否則會影響我們的主業務系統。那如何異步處理事件呢?
2. 事件異步處理
2.1 通過@Async注解
該種方式是最簡單的方式了,開啟異步功能,在基于注解的事件監聽方法上使用@Async注解。
開啟異步任務功能更
@EnableAsync
public class AppApplication {}
基于注解事件監聽
@Async
@EventListener({PackEvent.class})
public void packEventListener(PackEvent event) {
System.out.printf("%s - 事件發生了...%s%n", Thread.currentThread().getName(), event.getSource()) ;
}
執行結果
task-1 - 事件發生了...自定義Pack
線程名已經變為了task-1。task-前綴是異步線程的默認名。關于異步任務執行應用的線程池配置,查看下面這篇文章。
Spring任務調度&異步任務&Web異步請求三者如何配置線程池?
上面是基于注解的方式應用異步執行事件處理。對于在簡介中通過實現ApplicationListener接口的方式又該如何處理呢?
對于這種方式,我們可以通過兩種方式進行處理:
2.2 自定義線程池
- 在事件監聽處理程序中開啟異步線程
@Component
public class PackEventListener implements ApplicationListener<PackEvent> {
@Override
public void onApplicationEvent(PackEvent event) {
new Thread(() -> {
System.out.printf("%s觸發事件...%n", Thread.currentThread().getName()) ;
}).start() ;
}
}
- 自定義事件廣播器
@Bean
TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor() ;
taskExecutor.setThreadNamePrefix("pack-event-") ;
taskExecutor.setCorePoolSize(5) ;
taskExecutor.setQueueCapacity(100) ;
taskExecutor.setMaxPoolSize(5) ;
taskExecutor.initialize() ;
return taskExecutor ;
}
// 注意beanName必須為applicationEventMulticaster;下面的源碼中你將看到
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
SimpleApplicationEventMulticaster eventMulticaster(BeanFactory beanFactory) {
SimpleApplicationEventMulticaster eventMulticaster = new SimpleApplicationEventMulticaster(beanFactory) ;
eventMulticaster.setTaskExecutor(taskExecutor()) ;
return eventMulticaster ;
}
通過這種方式也可以實現事件處理程序異步執行。而這種方式的實現原理如下:
容器啟動中的核心方法refresh中
public abstract class AbstractApplicationContext {
public void refresh() {
// 初始化事件廣播器
initApplicationEventMulticaster();
}
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 判斷容器中是否存在beanName=applicationEventMulticaster
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
} else {
// 如果不存在則創建一個同步的執行器。
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
}
}
}
以上是本篇文章的全部內容,希望對你有幫助。
完畢?。?!