如何動(dòng)態(tài)加載@KafkaListener的topics

問(wèn)題來(lái)源

我司最近剛重構(gòu)完,終于有時(shí)間去償還欠下的技術(shù)債了修肠。

最先準(zhǔn)備改造的就是將一些原本應(yīng)該異步執(zhí)行而因重構(gòu)時(shí)間緊而被迫同步執(zhí)行的方法林说,通過(guò)消息隊(duì)列異步化。

在原來(lái)的老項(xiàng)目中蔼卡,我們使用的是kafka喊崖。因此我們對(duì)kafka的熟悉程度遠(yuǎn)勝于其他消息隊(duì)列,也正因此雇逞,我們?cè)谛孪到y(tǒng)中依然采用了kafka作為我們的消息隊(duì)列中間件荤懂。

而擺在我們面前的問(wèn)題是,該如何在Spring Boot環(huán)境下集成kafka呢塘砸?

spring-kafka作為spring的全家桶成員节仿,是目前最簡(jiǎn)單的集成kafka的方式,自然也成為了我們的首選掉蔬。

這個(gè)集成kafka的重任廊宪,最終落到我的組員頭上。

在前期的編碼階段女轿,他一帆風(fēng)順箭启,并沒(méi)有遇到什么問(wèn)題。但在后期測(cè)試的時(shí)候蛉迹,他發(fā)現(xiàn)了一個(gè)問(wèn)題傅寡。因?yàn)閠opics肯定會(huì)隨著業(yè)務(wù)的發(fā)展而不斷增加,所以@KafkaListener注解下的topics字段肯定不能像demo一樣寫死固定的topic北救。

他最初詢問(wèn)我的時(shí)候荐操,我也沒(méi)有很好的辦法,因?yàn)槲抑按罱╧afka的時(shí)候扭倾,topics是我通過(guò)寫代碼的方式加載到消費(fèi)者線程上去的淀零。但是在注解中,這個(gè)方法顯然連編譯都通不過(guò)膛壹。

然后他也嘗試過(guò)將topics寫在常量類中驾中,而不再寫在枚舉中。這樣當(dāng)然能解決問(wèn)題模聋,但是這么做肩民,對(duì)后續(xù)的調(diào)用者而言,簡(jiǎn)直是個(gè)災(zāi)難链方。

調(diào)用者不光需要在常量類中添加topic和維護(hù)topic對(duì)應(yīng)的業(yè)務(wù)消費(fèi)方法持痰,還需要額外將添加的topic加入到@KafkaListener注解中。

最后一步至關(guān)重要祟蚀,如果不將topic加到注解中工窍,listener將不會(huì)監(jiān)聽該topic割卖,也自然無(wú)法消費(fèi)對(duì)應(yīng)的topic了,但這最后一步顯然是最容易被大家遺忘的患雏。

解決方案

那么還有什么更好的方案么鹏溯?

那自然是有的。

既然topics字段需要一個(gè)字符串?dāng)?shù)組淹仑,那我們可以通過(guò)spel語(yǔ)言丙挽,例如topics = {"#{'${topics}'.split(',')}"}這么寫,然后通過(guò)字符串分割來(lái)動(dòng)態(tài)生成一個(gè)字符串?dāng)?shù)組匀借。

spel語(yǔ)言在spring中應(yīng)用廣泛颜阐,尤其是在注解中。我們之前也通過(guò)spel解決了分布式鎖中動(dòng)態(tài)參數(shù)的問(wèn)題吓肋。

但是這么寫帶來(lái)了另外一個(gè)問(wèn)題凳怨。 如果Spring在加載@KafkaListener對(duì)應(yīng)的消費(fèi)者bean時(shí)(下稱ConsumerListener),找不到topics是鬼,就會(huì)報(bào)『Could not resolve placeholder 'topics' in value "#{'${topics}'.split(',')}"』的錯(cuò)誤猿棉。

那么${topics}該如何被替換呢?

同學(xué)們應(yīng)該很快就能想到可以通過(guò)配置文件來(lái)處理這個(gè)問(wèn)題屑咳。

最簡(jiǎn)單的,自然是在application.properties中配置用逗號(hào)隔開的多個(gè)topic了弊琴。

但是這個(gè)解決方案兆龙,還是不太友好。雖然我們可以采用apollo或者disconf等分布式配置中心來(lái)管理配置文件敲董,但依然沒(méi)有解決很容易遺忘配置topic的問(wèn)題紫皇。

這時(shí)候,我們應(yīng)該回顧Spring Boot配置屬性加載的相關(guān)內(nèi)容了腋寨。

既然配置文件能解決${topics}被替換的問(wèn)題聪铺,那么加載優(yōu)先級(jí)更高的配置自然也可以解決該問(wèn)題。而優(yōu)先級(jí)比配置文件更高的配置中萄窜,我們可以發(fā)現(xiàn)Java系統(tǒng)參數(shù)(System.getProperties())是個(gè)可以利用的點(diǎn)铃剔。

我們可以新創(chuàng)建一個(gè)KafkaTopicConfig的配置類,加上@Configuration注解查刻,然后在該配置類初始化后通過(guò)System.setProperty("topics", topics)把topics加到系統(tǒng)參數(shù)中键兜。

具體在bean初始化后執(zhí)行指定方法做法有四種,實(shí)現(xiàn) InitializingBean接口定制初始化后的方法穗泵,通過(guò) <bean> 元素的 init-method屬性指定初始化后調(diào)用的操作普气,在指定方法上加上@PostConstruct來(lái)指定該方法在初始化后調(diào)用,以及最簡(jiǎn)單的構(gòu)造器佃延。

本文選擇了實(shí)現(xiàn) InitializingBean接口來(lái)完成該操作现诀。具體代碼如下:

@Configuration
public class KafkaTopicConfig implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
        String topics = Sets.newHashSet(KafkaTopicEnum.values()).stream()
                .map(KafkaTopicEnum::getTopic).collect(Collectors.joining(","));
        System.setProperty("topics", topics);
    }
}

然后還需要注意的一點(diǎn)就是夷磕,KafkaTopicConfig配置類必須在ConsumerListener類之前加載到Spring的容器內(nèi),否則依然會(huì)在加載ConsumerListener類時(shí)報(bào)『Could not resolve placeholder 'topics' in value "#{'${topics}'.split(',')}"』的錯(cuò)誤仔沿。

而Spring Boot的bean裝配規(guī)則是根據(jù)Application類所在的包位置從近到遠(yuǎn)進(jìn)行掃描的坐桩,所以如果KafkaTopicConfig所在目錄離Application的距離比ConsumerListener所在目錄更遠(yuǎn),就會(huì)導(dǎo)致ConsumerListener在KafkaTopicConfig之前加載于未。

為了避免出現(xiàn)這種情況撕攒,我們必須調(diào)整這兩個(gè)bean的加載順序。具體修改加載bean加載順序的方式有很多種烘浦,我們采用了在ConsumerListener類上加DependsOn注解的方式來(lái)解決抖坪,如下所示:

@DependsOn(value = "kafkaTopicConfig")

至此,動(dòng)態(tài)加載@KafkaListener的topics的問(wèn)題就被完美解決了闷叉。

好了擦俐,我們下一期再見,歡迎大家一起留言討論握侧。同時(shí)也歡迎點(diǎn)贊和送小星星~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚯瞧,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子品擎,更是在濱河造成了極大的恐慌埋合,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件萄传,死亡現(xiàn)場(chǎng)離奇詭異甚颂,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)秀菱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門振诬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人衍菱,你說(shuō)我怎么就攤上這事赶么。” “怎么了脊串?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵辫呻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我琼锋,道長(zhǎng)印屁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任斩例,我火速辦了婚禮雄人,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己础钠,他們只是感情好恰力,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著旗吁,像睡著了一般踩萎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上很钓,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天香府,我揣著相機(jī)與錄音,去河邊找鬼码倦。 笑死企孩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袁稽。 我是一名探鬼主播勿璃,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼推汽!你這毒婦竟也來(lái)了补疑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤歹撒,失蹤者是張志新(化名)和其女友劉穎莲组,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暖夭,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胁编,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鳞尔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡早直,死狀恐怖寥假,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情霞扬,我是刑警寧澤糕韧,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站喻圃,受9級(jí)特大地震影響萤彩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜斧拍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一雀扶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦愚墓、人聲如沸予权。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)扫腺。三九已至,卻和暖如春村象,著一層夾襖步出監(jiān)牢的瞬間笆环,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工厚者, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留躁劣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓籍救,卻偏偏與公主長(zhǎng)得像习绢,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝙昙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容