成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

我跟你說@RefreshScope跟Spring事件監聽一起用有坑!

開發 前端
實話實說,我在測試給我上報問題,到發現這個問題來自于 @RefreshScope 注解只用了 10 分鐘,如上文所說,我通過對比以前寫的 MemberLoginLogListener 監聽類,早早的定位到問題來自于 @RefreshScope 注解。

本文記錄一下我在 Spring 自帶的事件監聽類添加 @RefreshScope 注解時遇到的坑,原本這兩個東西單獨使用是各自安好,但當大家將它們組合在一起時,會發現我們的事件監聽代碼被重復執行。希望大家引以為鑒,避免重復踩坑。耐心看完,你一定會有所收獲!

前置描述

最近有一個用戶拉新的需求,需要在新用戶注冊時判斷用戶是否有對應的邀請關系,如果有則需要給新用戶贈送系統資源。

原有的用戶注冊邏輯里使用了 Spring 自帶的事件監聽工具,也就是 applicationEventPublisher(事件發布類)以及 ApplicationListener(事件監聽類),在用戶注冊完畢寫入用戶記錄并生成 token 后,會觸發 RegisterEvent(注冊事件)的發布。偽代碼如下,

// 1. 用戶注冊,寫入數據庫
RegisterResponseVO registerResponseVO = memberRegisterService.register(new RegisterRequestVO(request);

// 2. 生成token
String token = getToken(memberEntity.getId(), request.getSource());
log.info("login mobile {} login token {}", request.getMobile(), token);

// 3. 發布注冊事件,會觸發登錄日志監聽、優惠券贈送監聽等
applicationEventPublisher.publishEvent(new RegisterEvent(request, memberEntity, token));

由于之前代碼已經使用事件監聽邏輯,所以這里我們的新用戶注冊判斷邀請關系的邏輯就直接新建一個 NewUserInvitedListener 監聽類即可。偽代碼如下,

@Slf4j
@RefreshScope
@AllArgsConstructor
@Component
public class NewUserInvitedListener implements ApplicationListener<RegisterEvent> {

    @Async("asyncServiceExecutor")
    @Override
    public void onApplicationEvent(RegisterEvent registerEvent) {
        UserLoginRequestVO requestVO = registerEvent.getRequestVO();
        MemberEntity memberEntity = registerEvent.getMemberEntity();
        log.info("================ NewUserInvitedListener =============== registerEvent is {}", registerEvent);
        // 1. 校驗邏輯
        validateUser(memberEntity);
        // 2. 判斷用戶是否有邀請關系
        // 3. 如果有則贈送系統資源
        ...
    }
}

OK,代碼邏輯也不復雜,寫完提測交給測試下班(周五下午寫完)。

發現問題

周一一來,測試就在群里 @ 后端人員說是新用戶贈送的系統資源送了兩次,說實話我一開始是不太信的,直到我去查了日志,發現 NewUserInvitedListener 監聽類的日志確實被打印了兩次,也就是說我們的 NewUserInvitedListener 監聽類被觸發了兩次。

OK,到這里我們的問題就確確實實產生了,接下來就是解決問題。

解決思路

問題產生通常都有很多種解決方法,我們如何選擇一個最適合我們當前場景的方法才能體現出我們對業務、技術的理解。在這個監聽類重復觸發的場景里,就有多種解決方式,我簡單列舉幾個,

  1. 添加冪等處理,防止重復執行
  2. 加鎖,防止重復執行
  3. 解決下為什么監聽類會重復觸發

這三個解決方案各有優劣,通過對監聽類的業務邏輯添加冪等邏輯或者加鎖邏輯都是可以解決的,但是這不是問題根源,問題根源是在于監聽類為什么會被重復觸發。在本文中,我也將帶著大家一步一步探索并解決這個問題。

檢查下之前的事件監聽類是否也有重復觸發的問題

因為這個代碼是照著之前的邏輯寫的,新加的 NewUserInvitedListener 被發現重復觸發,那以前的 MemberLoginLogListener 是否也有重復觸發的問題。偽代碼如下,

@Slf4j
@Component
@AllArgsConstructor
public class MemberLoginLogListener implements ApplicationListener<RegisterEvent> {
    private MemberLoginLogService memberLoginLogService;

    @Async("asyncServiceExecutor")
    @Override
    public void onApplicationEvent(RegisterEvent event) {
        MemberEntity memberEntity = event.getMemberEntity();
        log.info("================ MemberLoginLogListener ===============, mobile is {}", memberEntity.getMobile());
        MemberLoginLogEntity memberLoginLogEntity = MemberLoginLogConvertor.buildLoginLogEntity(event.getRequestVO(),
                event.getMemberEntity());
        memberLoginLogEntity.setToken(event.getToken());
        memberLoginLogService.save(memberLoginLogEntity);
    }
}

查詢 MemberLoginLogListener 監聽類的日志,發現只有一次打印,說明之前寫的 MemberLoginLogListener 監聽類沒有重復觸發的問題,那這里就很奇怪了。對比一下 NewUserInvitedListener 監聽類與 MemberLoginLogListener 監聽類的差別,很明顯我們發現 NewUserInvitedListener 監聽類上多了一個 @RefreshScope 注解。

OK,問題有可能就是 @RefreshScope 注解導致,我們去掉 @RefreshScope 注解在看看日志打印。

去掉 @RefreshScope 注解

當我們去掉 @RefreshScope 注解后,神奇的事情發生了,NewUserInvitedListener 監聽類的日志打印正常了,只觸發了一次!OK,到這里我們也就發現了問題出在 @RefreshScope 注解上。

如何搜索問題

雖然我們知道了問題出在 @RefreshScope 注解上,但是我們怎么向搜索引擎描述這個問題嘞?

很多人發現了問題,但是不知道如何描述問題,怎么描述問題才能讓別人一聽就懂,從而能給你提供幫助。你需要把問題的重點描述出來,搜索引擎才能給予精準幫助。

在我們這個新用戶注冊判斷邀請關系的場景里,很顯然我們的搜索詞可以是 “spring 事件監聽重復觸發 @RefreshScope”可以看到我的搜索關鍵詞有 3 個,分別是 spring、事件監聽重復觸發以及 @RefreshScope。讓我們來看看搜索結果。

圖片圖片

前 5 個搜索結果中,只有第五個的標題可能符合我們的搜索內容,我們點進去看一看。

圖片圖片

很遺憾,跟我們的問題場景并不相符,我們并沒有搜索到我們想要的東西。在這里我們的搜索關鍵詞“spring 事件監聽重復觸發 @RefreshScope”并沒有給予我們幫助。

回到問題本身

既然我們的問題已經定位到了,在于 @RefreshScope 會導致監聽類的重復觸發,可是這個關鍵詞并沒有相關搜索結果,那么我們只能換個角度。

為什么會重復觸發?

在 NewUserInvitedListener 監聽類中,我們使用 @Component 注解,默認注冊了一個單例 bean,這個 bean 用于接收用戶注冊事件。既然 bean 是單一的,那就是說 Spring 發送了 2 次 RegisterEvent 事件嗎?結合上文提到的 MemberLoginLogListener 監聽類只觸發一次的日志,很顯然,Spring 只會發送了 1 次 RegisterEvent 事件。

難道說問題在于 Spring 里出現了兩個 NewUserInvitedListener 類型的 bean?

那么到這里恭喜我們終于定位到了重復觸發問題的根源。

如果大家了解 @RefreshScope 的原理相信大家已經猜出來了。

@RefreshScope 原理

Spring 中 @scope 注解的原理就是在創建 Scope=singleton 的 Bean 時,IOC 會保存實例在一個 Map 中,保證這個 Bean 在一個 IOC 上下文有且僅有一個實例。

SpringCloud 新增了一個自定義的作用域:refresh(可以理解為“動態刷新”),同樣用了一種獨特的方式改變了 Bean 的管理方式,使得其可以通過外部化配置(.properties)的刷新,在應用不需要重啟的情況下熱加載新的外部化配置的值。

這個 scope 是如何做到熱加載的呢?RefreshScope 主要做了以下動作:單獨管理 Bean 生命周期

創建 Bean 的時候如果是 RefreshScope 就緩存在一個專門管理的 ScopeMap 中,這樣就可以管理 Scope 是 Refresh 的 Bean 的生命周期了(所以含 RefreshScope 的其實一共創建了兩個 bean)。

重新創建 Bean

外部化配置刷新之后,會觸發一個動作,這個動作將上面的 ScopeMap 中的 Bean 清空,這樣這些 Bean 就會重新被 IOC 容器創建一次,使用最新的外部化配置的值注入類中,達到熱加載新值的效果。

看完 @RefreshScope 的原理相信大家已經知道了出現兩個 NewUserInvitedListener 類型 bean 的原因是在于 @RefreshScope 導致。這是由于 @RefreshScope 注解的內部實現創建了另外一個相同類型的 NewUserInvitedListener bean,導致我們的新用戶監聽邏輯被重復執行。

回到搜索關鍵詞

假如我是說假如,假如我們不知道 @RefreshScope 的原理,自然不知道項目中出現了兩個 NewUserInvitedListener 類型的 bean 是 @RefreshScope 導致。那么我們怎么通過搜索關鍵詞來找到這個問題嘞?

到這里也就是本文的重點所在,怎么通過搜索關鍵詞來解決我們的問題。

先定義問題

在這個場景里我們使用的是 Spring 項目,問題本質是 @RefreshScope 在 Spring 自帶的事件監聽類搭配使用時,由于 @RefreshScope 會導致 bean 重復進而導致重復觸發。

總結關鍵詞

在上面的先定義問題中,我們提煉一下關鍵詞,

  • Spring:這個關鍵詞在 Spring 項目中必帶,大家應該沒有意見把
  • @RefreshScope:我們的問題根源,搜索也得帶上
  • 生成同一個 bean:這是一個描述語句,簡要描述一下我們發現的問題

看一看搜索結果,

圖片圖片

點進第一個結果,

圖片圖片

OK,大功告成,看到我們框選中的地方了嗎,上文的 @RefreshScope 原理解釋,就是復制與這里。

貼一下原文地址:https://blog.csdn.net/m0_71777195/article/details/127223544

一些思考

實話實說,我在測試給我上報問題,到發現這個問題來自于 @RefreshScope 注解只用了 10 分鐘,如上文所說,我通過對比以前寫的 MemberLoginLogListener 監聽類,早早的定位到問題來自于 @RefreshScope 注解。可是到我完整修復這個問題,提交到測試環境,卻花了 2 個半小時,原因是因為我在研究這個問題的根源,這也是這篇文章的由來。

假如說這個問題發生在線上,那么我根本不可能花這么多時間來研究,我需要的就是迅速解決這個問題并修復上線,避免影響更多用戶。

一樣的,大家在遇到這種相似問題時,如果境況緊急出現在生產環境,大家本著對工作負責的態度,應該迅速解決并做故障復盤。如果是出現在測試環境我們可以本著對技術執著可以認真專研下這個問題。

其實我還想說的是在這個問題里,我能 10 分鐘定位到問題來自于 @RefreshScope 注解,可能也有運氣成分。但是很多情況下當我們照驢子畫馬寫代碼,發現出了問題時,這種情況大部分還是我們“畫蛇添足”導致。大家可以通過對比以前代碼迅速找出問題原因。

找出了問題后是如何解決問題。這篇文章里,我給大家講了講我的搜索關鍵詞心得。第一是講重點、第二是找到問題本質,這樣才能從搜索引擎嘴里找出我們想要的答案。

責任編輯:武曉燕 來源: 程序員wayn
相關推薦

2021-08-26 10:50:37

MySQLORDER BYIMIT

2021-05-07 11:29:54

MacFlutter開發

2021-01-13 09:07:32

MySQLOrderLimit

2021-11-30 07:51:29

氣球數量空間

2013-07-30 11:32:02

2012-07-27 13:36:00

Office操作系統

2023-03-28 08:12:06

優化系統IOPS

2022-03-01 11:00:12

索引MySQL設計

2015-01-08 09:18:25

DockerRocket容器技術

2015-07-15 09:28:22

云計算原型設計物聯網

2024-11-28 09:57:50

C#事件發布器

2021-04-12 18:03:39

Nginx架構負載

2020-12-01 09:40:15

Harmony

2020-06-11 18:35:23

C++編程語言

2021-09-14 17:16:21

Java 同步工具類

2021-09-11 19:02:34

Hook使用版本

2021-07-02 20:46:06

Go接口動態

2024-10-05 07:00:00

OpenAIGPT-4o人工智能

2020-12-03 11:34:12

Ability vs

2020-09-28 16:51:11

Netty驅動網絡
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 天天干天天想 | 午夜影视大全 | 日本精品视频在线 | 鲁大师一区影视 | 成人免费视频在线观看 | 成年人的视频免费观看 | 美国一级毛片a | 在线观看视频中文字幕 | 久久久久久综合 | www.午夜 | 午夜视频在线 | 国产精品视频一 | 午夜视频在线免费观看 | 国产精品美女久久久久久免费 | 国产美女高潮 | 欧美视频 亚洲视频 | 日韩欧美精品 | 狠狠操狠狠操 | 99热激情| 久久久久久网站 | 91在线视频免费观看 | 欧美一级片黄色 | 91大神在线资源观看无广告 | 亚洲在线一区 | 日韩一二三区视频 | 久久国产精品免费一区二区三区 | 国产精品一区二区在线免费观看 | 亚洲国产精品91 | 日本不卡一区二区三区 | 99久久国产综合精品麻豆 | 中文字幕av亚洲精品一部二部 | 日本三级视频 | 亚洲欧美激情精品一区二区 | 四虎影视1304t | 国产一区二区在线视频 | 97久久精品午夜一区二区 | 97视频在线看 | 欧美在线视频一区 | 四季久久免费一区二区三区四区 | 中文字幕蜜臀av | 免费国产黄|