同時(shí)使用spring-boot-starter-quartz和spring-boot-starter-websocket報(bào)錯(cuò)

背景

起初這個(gè)問(wèn)題是同事在項(xiàng)目中發(fā)現(xiàn)的公给,搜索引擎了一下也就解決了這個(gè)問(wèn)題基协,但是我同事比較好學(xué)嗅蔬,解決這個(gè)Bug的帖子很多,但是卻沒(méi)有提及為什么會(huì)出現(xiàn)此情況疾就,遂問(wèn)我能不能幫他看看原因澜术,那我當(dāng)然就義不容辭擼了一下源碼。

分析問(wèn)題

先看一眼報(bào)錯(cuò)吧:

2020-12-08 18:50:09.535 [main] ERROR org.springframework.boot.SpringApplication [line:837] - Application run failed
org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'defaultSockJsTaskScheduler' is expected to be of type 'org.springframework.scheduling.TaskScheduler' but was actually of type 'org.springframework.beans.factory.support.NullBean'
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:399)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1175)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1142)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.resolveSchedulerBean(ScheduledAnnotationBeanPostProcessor.java:327)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.finishRegistration(ScheduledAnnotationBeanPostProcessor.java:256)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:233)
    at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:105)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:404)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:361)
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:898)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:554)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)

首先我們從報(bào)錯(cuò)入手猬腰,BeanNotOfRequiredTypeException顯然是Bean的類(lèi)型不對(duì)鸟废,然后我們?cè)倬唧w看名稱(chēng)叫defaultSockJsTaskSchedulerBean類(lèi)型不對(duì),那搜索大法姑荷,先分別從@EnableWebSocket@EnableScheduling點(diǎn)進(jìn)行盒延,把這兩個(gè)包的源碼下載了再說(shuō)缩擂,然后全局搜索defaultSockJsTaskScheduler,這時(shí)候找定義叫defaultSockJsTaskScheduler的添寺,很容易定位到org.springframework.web.socket.config.annotation.WebSocketConfigurationSupport#defaultSockJsTaskScheduler胯盯,它的定義如下:

@Bean
@Nullable
public TaskScheduler defaultSockJsTaskScheduler() {
    if (initHandlerRegistry().requiresTaskScheduler()) {
        ThreadPoolTaskScheduler threadPoolScheduler = new ThreadPoolTaskScheduler();
        threadPoolScheduler.setThreadNamePrefix("SockJS-");
        threadPoolScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
        threadPoolScheduler.setRemoveOnCancelPolicy(true);
        this.scheduler = threadPoolScheduler;
    }
    return this.scheduler;
}

這里的邏輯具體分析一下會(huì)知道,如果if條件不滿足计露,這里將返回null博脑,也就是會(huì)創(chuàng)建一個(gè)NullBean在容器中作為TaskScheduler

我們找到了NullBean產(chǎn)生的地方票罐,那么為什么單獨(dú)使用兩個(gè)包都不會(huì)有問(wèn)題呢叉趣,只能解釋?zhuān)?dāng)單獨(dú)使用spring-boot-starter-websocket時(shí),即使創(chuàng)建了NullBean但是并不會(huì)被使用该押,而增加spring-boot-starter-quartz的過(guò)程肯定使用到了TaskScheduler疗杉。而單獨(dú)使用spring-boot-starter-quartz又沒(méi)有問(wèn)題,所以肯定還有地方實(shí)例化了TaskScheduler蚕礼,那么進(jìn)入TaskScheduler烟具,然后看看那些地方用到了它:

TaskScheduler引用

)

很容易猜到TaskSchedulingAutoConfiguration,我們進(jìn)去看到了如下:

@Bean
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class, ScheduledExecutorService.class })
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
    return builder.build();
}

單獨(dú)有spring-boot-starter-websocket時(shí)闻牡,會(huì)創(chuàng)建defaultSockJsTaskScheduler净赴,但是沒(méi)地方用到,所以不會(huì)有問(wèn)題罩润,而引入spring-boot-starter-quartz時(shí)玖翅,由于上面產(chǎn)生的NullBean導(dǎo)致這個(gè)條件裝配失效,所以不會(huì)產(chǎn)生合適的TaskScheduler實(shí)例割以。由于@EnableScheduling的存在金度,導(dǎo)致org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor#finishRegistration會(huì)被執(zhí)行到,為啥不用細(xì)說(shuō)了吧(不明白可以截圖找我問(wèn))严沥,此時(shí)該方法中會(huì)執(zhí)行this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));代碼猜极,這里的resolveSchedulerBean將會(huì)去容器中找TaskScheduler的實(shí)例,通過(guò)名稱(chēng)找到了defaultSockJsTaskScheduler消玄,但是他的類(lèi)型卻是個(gè)NullBean此時(shí)就會(huì)觸發(fā)上述錯(cuò)誤跟伏。

這便是導(dǎo)致這個(gè)Bug的原因了,有些人可能疑惑翩瓜,你上面那些類(lèi)咋知道的受扳,emmmm,找這種問(wèn)題一般步驟就是猜想兔跌、驗(yàn)證勘高、整理。所以先猜,再Debug驗(yàn)證华望。

解決問(wèn)題

那既然知道了為什么會(huì)出現(xiàn)這個(gè)錯(cuò)了蕊蝗,就容易修復(fù)了,就是因?yàn)榇嬖诹艘粋€(gè)NullBean類(lèi)型的TaskScheduler實(shí)例赖舟,在查找的時(shí)候出現(xiàn)了該錯(cuò)誤蓬戚,那么我們只需要自己創(chuàng)建一個(gè)正確類(lèi)型就好了:

@Bean
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
    return builder.build();
}

當(dāng)然,根據(jù)前面提到的if條件不滿足才會(huì)出現(xiàn)NullBean建蹄,那么自然還有種方案碌更,那就是讓if條件滿足,即實(shí)現(xiàn)一個(gè)WebSocketConfigurer也可以解決這個(gè)問(wèn)題(看一下if附近的注釋?zhuān)瑢?shí)現(xiàn)應(yīng)該不難洞慎,不給案例了)痛单。

可以可以,問(wèn)題解決劲腿,愉快的告訴同事咋回事就完事了旭绒。

一點(diǎn)思考

然鵝,此時(shí)另一個(gè)念頭冒出來(lái)了焦人。這不應(yīng)該是Spring FrameworkBug嗎挥吵,雖然在幾個(gè)使用TaskScheduler的地方都說(shuō)明可以為null,但是可以為null和創(chuàng)建了一個(gè)NullBean類(lèi)型作為TaskScheduler的實(shí)例花椭,并導(dǎo)致了真正在使用的時(shí)候產(chǎn)生了錯(cuò)誤忽匈,這妥妥的Spring FrameworkBug啊。那為啥明明是Bug矿辽,網(wǎng)上也有不少關(guān)于這個(gè)錯(cuò)誤的處理方案丹允,那么官方卻沒(méi)有解決呢,看了一下版本袋倔,這個(gè)問(wèn)題存在好些個(gè)版本了雕蔽,難道沒(méi)人提過(guò)issue,看了一眼好像還真是宾娜。那E!前塔!提OА!华弓!

說(shuō)干就干食零,啪提個(gè)issue,官方回復(fù)很快该抒,經(jīng)過(guò)討論后慌洪,據(jù)說(shuō)會(huì)在5.3.3處理該問(wèn)題,比較曲折的是凑保,一開(kāi)始提在Spring Framework下冈爹,后來(lái)被遷到了Spring Boot下,然后討論完又認(rèn)為是Spring Framework的問(wèn)題欧引,所以處理者又重新開(kāi)了個(gè)新的issue频伤。

結(jié)語(yǔ)

  • 找問(wèn)題要多猜想驗(yàn)證
  • 敢于質(zhì)疑,不論對(duì)于Spring Framework還是對(duì)于其他大佬芝此,不能一味相信憋肖,要有自己的判斷力

好了,就這樣吧婚苹,又水一篇文章岸更,唉~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市膊升,隨后出現(xiàn)的幾起案子怎炊,更是在濱河造成了極大的恐慌,老刑警劉巖廓译,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件评肆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡非区,警方通過(guò)查閱死者的電腦和手機(jī)瓜挽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)征绸,“玉大人久橙,你說(shuō)我怎么就攤上這事〈醯妫” “怎么了剥汤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)排惨。 經(jīng)常有香客問(wèn)我吭敢,道長(zhǎng),這世上最難降的妖魔是什么暮芭? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任鹿驼,我火速辦了婚禮,結(jié)果婚禮上辕宏,老公的妹妹穿的比我還像新娘畜晰。我一直安慰自己,他們只是感情好瑞筐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布凄鼻。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪块蚌。 梳的紋絲不亂的頭發(fā)上闰非,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音峭范,去河邊找鬼财松。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纱控,可吹牛的內(nèi)容都是我干的辆毡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼甜害,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼舶掖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起尔店,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤访锻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后闹获,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體期犬,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年避诽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了龟虎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沙庐,死狀恐怖鲤妥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拱雏,我是刑警寧澤棉安,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站铸抑,受9級(jí)特大地震影響贡耽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鹊汛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一蒲赂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刁憋,春花似錦滥嘴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)镊叁。三九已至,卻和暖如春走触,著一層夾襖步出監(jiān)牢的瞬間意系,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工饺汹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痰催。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓兜辞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親夸溶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子逸吵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

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