網站首頁 編程語言 正文
1,基本原理
日常開發中,我們有時會使用SpringEvent對業務解耦,使我們的代碼更加高內聚低耦合,不過如果對其運行原理不清楚,那么在使用的過程中,一不留神就會出現一些bug。
今天我們回顧一下SpringEvent使用的基本原理,需要優化的點,以及非常常見的兩種錯誤。
Spring的事件模式其實很簡單,我們創建一個Event事件,當Event發生時,廣播器對事件進行發布,然后對應的Listener進行處理即可。
Spring的事件一共有三個組件:
1,Event:用于定于我們的事件,比如ApplicationEvent或者通過繼承ApplicationEvent定義我們自己的事件。
2,廣播器Multicaster:當事件發生時,將事件廣播出去。
3,監聽器Listener:監聽和處理廣播器廣播的事件。
2,基本用法
第一步,首先定義一個Event事件,
@Getter
@Setter
public class MessageEvent extends ApplicationEvent {
private String content;
public MessageEvent(String content) {
super(new Object());
this.content = content;
}
}
第二步,定義一個Listener對事件進行監聽,
@Component
public class MessageListener {
@EventListener
public void listen(MessageEvent messageEvent) {
System.out.println("收到消息:" + messageEvent.getContent());
}
}
最后在我們的業務邏輯需要的地方,就可以發布事件了。
@RestController
@RequestMapping(value = "/demo")
public class DemoController {
@Resource
private ApplicationContext applicationContext;
@PostMapping(value = "/send")
public ResponseEntity sendMessage() {
//.....
//處理一些業務邏輯之后,發送通知消息
MessageEvent messageEvent = new MessageEvent("發布一條測試消息");
this.applicationContext.publishEvent(messageEvent);
return ResponseEntity.ok().build();
}
}
3,需要注意的點
一,對于同一個Event,我們可以定義多個Listener,多個Listener之間可以通過@Order來指定順序,order的Value值越小,執行的優先級就越高。
二,我們可以使用@EventListener輕松標記一個方法作為監聽器,但是默認情況下,它是同步執行的,所以如果發布事件的方法處于事務中,那么事務會在監聽器方法執行完畢之后才提交。
有些情況下,我們希望事件發布之后就由監聽器去處理,而不要影響原有的事務,也就是說希望事務及時提交。
這時我們可以使用@TransactionalEventListener來定義一個監聽器。
@Component
public class MessageListener {
//上層事務執行完畢之后再執行
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void listen(MessageEvent messageEvent) {
System.out.println(Thread.currentThread().getName());
System.out.println("收到消息:" + messageEvent.getContent());
}
}
三,默認情況下,@EventListener定義的方法是同步執行的,如果我們想通過異步的方式執行一個監聽器的方法,可以在方法上加上@Async注解(記得在啟動類上加上@EnableAsync開啟異步執行配置)。
需要注意的是,使用@Async時,必須為其配置線程池,否則用的還是默認的線程。
如@Async(value = "taskExecutor"),此時Listener就會被分配到taskExecutor的線程池中執行。
使用@Async異步執行的同時,還會帶來另外兩個問題,需要大家注意:
1,如果Listener執行過程中拋出了異常,由于是異步執行,異常并不會被事件發布方捕獲。
2,異步執行時,方法的返回值不能用來發布后續事件,如果需要處理結果去發布另一個事件,需要我們手動去發布。
4,常見錯誤一:錯誤的監聽一個并不會拋出的事件
有時我們希望監聽Spring的啟動事件,做一些初始化操作。于是有的同學可能定義了這樣一個Listener:
@Component
public class MessageListener {
@EventListener
public void listen2(ContextStartedEvent event) {
System.out.println("Spring啟動了," + event.toString());
}
}
不過,雖然名字看起來似乎是一個上下文啟動時的事件,但是Spring啟動時并不會發布這個事件,我們啟動項目看下控制臺是否會打印日志:
可以看到,Spring項目啟動后,并沒有打印任何日志。
其實Spring項目啟動后發布的真正Event是ContextRefreshedEvent,我們修改下代碼再看一下結果:
這時,控制臺打印出了我們想要的日志。
在創建監聽事件時,一定要確保監聽的Event是正確的,否則就會監聽不到對應的事件。
5,常見錯誤二:由于異常導致的事件傳播丟失
即使我們保證事件會被監聽器真正的捕獲,但是某些情況下,事件會因為異常導致傳播丟失。
如上圖所示,我們定義了兩個Listener,原本期望按照order定義的順序,將消息傳播依次傳播。然而因為一些原因,Listener1中的方法拋出了異常,導致Listener2無法接收到消息了。
這是因為:默認情況下處理器的執行是順序執行的,在執行過程中,如果一個監聽器執行拋出了異常,則后續監聽器就得不到被執行的機會了。
我們可以通過SimpleApplicationEventMulticaster中的multicastEvent方法看一下事件是如何傳播的,
通過上圖可以看出,如果在沒有定義線程池的情況下,在invokeListener方法中會調用doInvokeListener方法去執行真正的邏輯,在doInvokeListener方法中,如果拋出了異常,會導致后的Listener失效。
針對異常這種情況又該如何處理我們的代碼呢?
有三種方法:
1,使用try catch捕獲異常,只要Listener方法不拋出異常,自然每個Listener都可以收到廣播的消息。
2,使用@Async異步執行,通過上面的源碼可以看到,如果定義的線程池,那么每一個Listener都會在一個線程中執行,每個線程之后是相互獨立的,自然不會影響別人。
3,通過ErrorHandler處理掉異常,保證后面的Listener不受影響。
總結
本文主要講解了SpringEvent基本的使用方法,和平常開發中可能會遇到的一些問題。總的來說,Spring為了讓大家用的更輕松,考慮了各種可能發生的情況,但是如果大家不了解背后的實現原理,就可能發生一些本不該出現的bug。
原文鏈接:https://juejin.cn/post/7175502167602626618
相關推薦
- 2022-03-28 Python?format字符串格式化函數的使用_python
- 2022-06-01 idea對CPU的占用率過大問題的解決方法_相關技巧
- 2022-12-01 Git基礎學習之分支操作的示例詳解_相關技巧
- 2022-05-26 Flutter?Drawer抽屜菜單示例詳解_Android
- 2023-06-05 python中xlwt模塊的具體用法_python
- 2022-05-22 使用Redis實現點贊取消點贊的詳細代碼_Redis
- 2022-08-18 Linux阿里云服務器中安裝Nginx命令的詳細過程_服務器其它
- 2022-12-04 React使用refs操作DOM方法詳解_React
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支