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

我跟你說@RefreshScope跟Spring事件監(jiān)聽一起用有坑!

開發(fā) 前端
實(shí)話實(shí)說,我在測(cè)試給我上報(bào)問題,到發(fā)現(xiàn)這個(gè)問題來自于 @RefreshScope 注解只用了 10 分鐘,如上文所說,我通過對(duì)比以前寫的 MemberLoginLogListener 監(jiān)聽類,早早的定位到問題來自于 @RefreshScope 注解。

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

前置描述

最近有一個(gè)用戶拉新的需求,需要在新用戶注冊(cè)時(shí)判斷用戶是否有對(duì)應(yīng)的邀請(qǐng)關(guān)系,如果有則需要給新用戶贈(zèng)送系統(tǒng)資源。

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

// 1. 用戶注冊(cè),寫入數(shù)據(jù)庫
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. 發(fā)布注冊(cè)事件,會(huì)觸發(fā)登錄日志監(jiān)聽、優(yōu)惠券贈(zèng)送監(jiān)聽等
applicationEventPublisher.publishEvent(new RegisterEvent(request, memberEntity, token));

由于之前代碼已經(jīng)使用事件監(jiān)聽邏輯,所以這里我們的新用戶注冊(cè)判斷邀請(qǐng)關(guān)系的邏輯就直接新建一個(gè) NewUserInvitedListener 監(jiān)聽類即可。偽代碼如下,

@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. 校驗(yàn)邏輯
        validateUser(memberEntity);
        // 2. 判斷用戶是否有邀請(qǐng)關(guān)系
        // 3. 如果有則贈(zèng)送系統(tǒng)資源
        ...
    }
}

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

發(fā)現(xiàn)問題

周一一來,測(cè)試就在群里 @ 后端人員說是新用戶贈(zèng)送的系統(tǒng)資源送了兩次,說實(shí)話我一開始是不太信的,直到我去查了日志,發(fā)現(xiàn) NewUserInvitedListener 監(jiān)聽類的日志確實(shí)被打印了兩次,也就是說我們的 NewUserInvitedListener 監(jiān)聽類被觸發(fā)了兩次。

OK,到這里我們的問題就確確實(shí)實(shí)產(chǎn)生了,接下來就是解決問題。

解決思路

問題產(chǎn)生通常都有很多種解決方法,我們?nèi)绾芜x擇一個(gè)最適合我們當(dāng)前場(chǎng)景的方法才能體現(xiàn)出我們對(duì)業(yè)務(wù)、技術(shù)的理解。在這個(gè)監(jiān)聽類重復(fù)觸發(fā)的場(chǎng)景里,就有多種解決方式,我簡(jiǎn)單列舉幾個(gè),

  1. 添加冪等處理,防止重復(fù)執(zhí)行
  2. 加鎖,防止重復(fù)執(zhí)行
  3. 解決下為什么監(jiān)聽類會(huì)重復(fù)觸發(fā)

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

檢查下之前的事件監(jiān)聽類是否也有重復(fù)觸發(fā)的問題

因?yàn)檫@個(gè)代碼是照著之前的邏輯寫的,新加的 NewUserInvitedListener 被發(fā)現(xiàn)重復(fù)觸發(fā),那以前的 MemberLoginLogListener 是否也有重復(fù)觸發(fā)的問題。偽代碼如下,

@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 監(jiān)聽類的日志,發(fā)現(xiàn)只有一次打印,說明之前寫的 MemberLoginLogListener 監(jiān)聽類沒有重復(fù)觸發(fā)的問題,那這里就很奇怪了。對(duì)比一下 NewUserInvitedListener 監(jiān)聽類與 MemberLoginLogListener 監(jiān)聽類的差別,很明顯我們發(fā)現(xiàn) NewUserInvitedListener 監(jiān)聽類上多了一個(gè) @RefreshScope 注解。

OK,問題有可能就是 @RefreshScope 注解導(dǎo)致,我們?nèi)サ?@RefreshScope 注解在看看日志打印。

去掉 @RefreshScope 注解

當(dāng)我們?nèi)サ?@RefreshScope 注解后,神奇的事情發(fā)生了,NewUserInvitedListener 監(jiān)聽類的日志打印正常了,只觸發(fā)了一次!OK,到這里我們也就發(fā)現(xiàn)了問題出在 @RefreshScope 注解上。

如何搜索問題

雖然我們知道了問題出在 @RefreshScope 注解上,但是我們?cè)趺聪蛩阉饕婷枋鲞@個(gè)問題嘞?

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

在我們這個(gè)新用戶注冊(cè)判斷邀請(qǐng)關(guān)系的場(chǎng)景里,很顯然我們的搜索詞可以是 “spring 事件監(jiān)聽重復(fù)觸發(fā) @RefreshScope”可以看到我的搜索關(guān)鍵詞有 3 個(gè),分別是 spring、事件監(jiān)聽重復(fù)觸發(fā)以及 @RefreshScope。讓我們來看看搜索結(jié)果。

圖片圖片

前 5 個(gè)搜索結(jié)果中,只有第五個(gè)的標(biāo)題可能符合我們的搜索內(nèi)容,我們點(diǎn)進(jìn)去看一看。

圖片圖片

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

回到問題本身

既然我們的問題已經(jīng)定位到了,在于 @RefreshScope 會(huì)導(dǎo)致監(jiān)聽類的重復(fù)觸發(fā),可是這個(gè)關(guān)鍵詞并沒有相關(guān)搜索結(jié)果,那么我們只能換個(gè)角度。

為什么會(huì)重復(fù)觸發(fā)?

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

難道說問題在于 Spring 里出現(xiàn)了兩個(gè) NewUserInvitedListener 類型的 bean?

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

如果大家了解 @RefreshScope 的原理相信大家已經(jīng)猜出來了。

@RefreshScope 原理

Spring 中 @scope 注解的原理就是在創(chuàng)建 Scope=singleton 的 Bean 時(shí),IOC 會(huì)保存實(shí)例在一個(gè) Map 中,保證這個(gè) Bean 在一個(gè) IOC 上下文有且僅有一個(gè)實(shí)例。

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

這個(gè) scope 是如何做到熱加載的呢?RefreshScope 主要做了以下動(dòng)作:?jiǎn)为?dú)管理 Bean 生命周期

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

重新創(chuàng)建 Bean

外部化配置刷新之后,會(huì)觸發(fā)一個(gè)動(dòng)作,這個(gè)動(dòng)作將上面的 ScopeMap 中的 Bean 清空,這樣這些 Bean 就會(huì)重新被 IOC 容器創(chuàng)建一次,使用最新的外部化配置的值注入類中,達(dá)到熱加載新值的效果。

看完 @RefreshScope 的原理相信大家已經(jīng)知道了出現(xiàn)兩個(gè) NewUserInvitedListener 類型 bean 的原因是在于 @RefreshScope 導(dǎo)致。這是由于 @RefreshScope 注解的內(nèi)部實(shí)現(xiàn)創(chuàng)建了另外一個(gè)相同類型的 NewUserInvitedListener bean,導(dǎo)致我們的新用戶監(jiān)聽邏輯被重復(fù)執(zhí)行。

回到搜索關(guān)鍵詞

假如我是說假如,假如我們不知道 @RefreshScope 的原理,自然不知道項(xiàng)目中出現(xiàn)了兩個(gè) NewUserInvitedListener 類型的 bean 是 @RefreshScope 導(dǎo)致。那么我們?cè)趺赐ㄟ^搜索關(guān)鍵詞來找到這個(gè)問題嘞?

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

先定義問題

在這個(gè)場(chǎng)景里我們使用的是 Spring 項(xiàng)目,問題本質(zhì)是 @RefreshScope 在 Spring 自帶的事件監(jiān)聽類搭配使用時(shí),由于 @RefreshScope 會(huì)導(dǎo)致 bean 重復(fù)進(jìn)而導(dǎo)致重復(fù)觸發(fā)。

總結(jié)關(guān)鍵詞

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

  • Spring:這個(gè)關(guān)鍵詞在 Spring 項(xiàng)目中必帶,大家應(yīng)該沒有意見把
  • @RefreshScope:我們的問題根源,搜索也得帶上
  • 生成同一個(gè) bean:這是一個(gè)描述語句,簡(jiǎn)要描述一下我們發(fā)現(xiàn)的問題

看一看搜索結(jié)果,

圖片圖片

點(diǎn)進(jìn)第一個(gè)結(jié)果,

圖片圖片

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

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

一些思考

實(shí)話實(shí)說,我在測(cè)試給我上報(bào)問題,到發(fā)現(xiàn)這個(gè)問題來自于 @RefreshScope 注解只用了 10 分鐘,如上文所說,我通過對(duì)比以前寫的 MemberLoginLogListener 監(jiān)聽類,早早的定位到問題來自于 @RefreshScope 注解。可是到我完整修復(fù)這個(gè)問題,提交到測(cè)試環(huán)境,卻花了 2 個(gè)半小時(shí),原因是因?yàn)槲以谘芯窟@個(gè)問題的根源,這也是這篇文章的由來。

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

一樣的,大家在遇到這種相似問題時(shí),如果境況緊急出現(xiàn)在生產(chǎn)環(huán)境,大家本著對(duì)工作負(fù)責(zé)的態(tài)度,應(yīng)該迅速解決并做故障復(fù)盤。如果是出現(xiàn)在測(cè)試環(huán)境我們可以本著對(duì)技術(shù)執(zhí)著可以認(rèn)真專研下這個(gè)問題。

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

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

責(zé)任編輯:武曉燕 來源: 程序員wayn
相關(guān)推薦

2021-08-26 10:50:37

MySQLORDER BYIMIT

2021-05-07 11:29:54

MacFlutter開發(fā)

2021-01-13 09:07:32

MySQLOrderLimit

2021-11-30 07:51:29

氣球數(shù)量空間

2013-07-30 11:32:02

2012-07-27 13:36:00

Office操作系統(tǒng)

2023-03-28 08:12:06

優(yōu)化系統(tǒng)IOPS

2022-03-01 11:00:12

索引MySQL設(shè)計(jì)

2015-01-08 09:18:25

DockerRocket容器技術(shù)

2015-07-15 09:28:22

云計(jì)算原型設(shè)計(jì)物聯(lián)網(wǎng)

2024-11-28 09:57:50

C#事件發(fā)布器

2021-04-12 18:03:39

Nginx架構(gòu)負(fù)載

2020-12-01 09:40:15

Harmony

2020-06-11 18:35:23

C++編程語言

2024-10-05 07:00:00

OpenAIGPT-4o人工智能

2021-09-14 17:16:21

Java 同步工具類

2021-09-11 19:02:34

Hook使用版本

2021-07-02 20:46:06

Go接口動(dòng)態(tài)

2020-12-03 11:34:12

Ability vs

2020-09-28 16:51:11

Netty驅(qū)動(dòng)網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 秋霞电影院午夜伦 | 成人h免费观看视频 | 性色视频| 黄色在线免费网站 | 国产日韩精品在线 | 国产日产精品一区二区三区四区 | 日韩一区在线视频 | 懂色tv| 久久精品亚洲欧美日韩久久 | 国产极品粉嫩美女呻吟在线看人 | 日本免费黄色一级片 | 欧美午夜影院 | 欧美一区二区另类 | 中文字幕av在线一二三区 | 久久久一区二区三区四区 | 中文字幕在线免费 | 天天综合久久网 | 欧美日韩在线观看视频网站 | 亚洲一区不卡在线 | 精品乱码久久久久 | 国产一级视频 | 国产亚洲精品久久久久久豆腐 | 欧洲一区二区在线 | 久久国产婷婷国产香蕉 | 国产精品夜间视频香蕉 | 国产一区二 | 国产目拍亚洲精品99久久精品 | 玖玖视频免费 | 欧美日韩综合一区 | 91在线免费视频 | 精品国产乱码久久久久久久久 | 影音先锋亚洲资源 | 亚洲 一区 | 精品一区二区视频 | 色婷婷综合久久久中字幕精品久久 | 91久久精品国产91久久 | 国产精品福利一区二区三区 | 欧美日韩一区精品 | 怡红院怡春院一级毛片 | 超碰欧美| 在线欧美视频 |