目啟動(dòng)報(bào) Cannot invoke “com.alibaba.nacos.client.naming.backups.FailoverDataSource.getSwitch()“...)
解決項(xiàng)目啟動(dòng)報(bào)java.lang.NullPointerException: Cannot invoke com.alibaba.nacos.client.naming.backups.FailoverDataSource.getSwitch() because the return value of com.alibaba.nacos.client.naming.backups.FailoverReactor.access$000(com.alibaba.nacos.client.naming.backups.FailoverReactor) is null問題發(fā)現(xiàn)問題解決排查方式一在本地或者測(cè)試環(huán)境模擬生產(chǎn)環(huán)境排查方式二使用命令java -jar執(zhí)行一下生產(chǎn)的包問題發(fā)現(xiàn)最近在生產(chǎn)環(huán)境啟動(dòng)時(shí)報(bào)了一個(gè)錯(cuò)誤束手無(wú)策因?yàn)楸镜睾蜏y(cè)試環(huán)境都沒有出現(xiàn)這個(gè)問題。完整的錯(cuò)誤信息如圖環(huán)境如下Nacos版本Nacos2.2.3SpringCloud版本Spring Cloud 2023.0.13JDK版本17問題解決本來(lái)以為baidu能給我答案最后發(fā)現(xiàn)根本搜不到有關(guān)該錯(cuò)誤的文章。然后就開始了苦逼的排查之路。排查方式一在本地或者測(cè)試環(huán)境模擬生產(chǎn)環(huán)境然后我本地搭建了一個(gè)和生產(chǎn)環(huán)境一模一樣的Nacos版本然后將配置文件改成本地的地址示例代碼如下spring:cloud:nacos:config:server-addr:127.0.0.1:8848discovery:server-addr:127.0.0.1:8848然后啟動(dòng)項(xiàng)目未發(fā)現(xiàn)異常情況。排查方式二使用命令java -jar執(zhí)行一下生產(chǎn)的包找到從生產(chǎn)上下載的包打開cmd窗口執(zhí)行命令java-jarxxx.jar然后發(fā)現(xiàn)問題出現(xiàn)了后來(lái)我又陸續(xù)切換了好幾個(gè)分支打包后重試均出現(xiàn)該問題。神奇的很命令啟動(dòng)有這個(gè)問題Idea啟動(dòng)就沒這個(gè)問題。只要能復(fù)現(xiàn)問題就好辦然后就開始定位問題我把該項(xiàng)目的所有代碼全刪了然后打包重新運(yùn)行發(fā)現(xiàn)正常啟動(dòng)沒有異常錯(cuò)誤。然后就一個(gè)類一個(gè)類的刪最后定位到一個(gè)初始化Bean的方法上面示例代碼如下RestControllerpublicclassAppControllerimplementsInitializingBean{ResourceprivateEchoServiceechoService;OverridepublicvoidafterPropertiesSet()throwsException{echoService.echo(hello);}}當(dāng)我把a(bǔ)fterPropertiesSet()方法中的代碼注釋打包后啟動(dòng)依舊沒有發(fā)現(xiàn)問題。最終定位在RPC接口的問題上。回過頭去看日志你會(huì)發(fā)現(xiàn)在日志中第一次出現(xiàn)錯(cuò)誤的位置結(jié)合上下文基本上可以定位問題所在位置。然后再來(lái)分析為什么會(huì)出現(xiàn)這種情況在Spring Bean的afterPropertiesSet()初始化階段訪問了Nacos客戶端相關(guān)資源Nacos服務(wù)發(fā)現(xiàn)來(lái)獲取服務(wù)地址間接依賴Nacos但此時(shí)Nacos客戶端尚未完成初始化導(dǎo)致依賴的FailoverReactor等組件為null最終觸發(fā)空指針異常。雖然Nacos沒有注冊(cè)完成那為什么RPC接口卻成功響應(yīng)結(jié)果了呢Nacos服務(wù)發(fā)現(xiàn)的核心功能已臨時(shí)可用但內(nèi)部故障轉(zhuǎn)移Failover組件的初始化滯后導(dǎo)致首次調(diào)用成功后后續(xù)的故障轉(zhuǎn)移檢查觸發(fā)空指針異常。為什么該錯(cuò)誤不會(huì)影響項(xiàng)目正常運(yùn)行Nacos客戶端的FailoverReactor故障轉(zhuǎn)移組件屬于輔助功能其初始化異常通常發(fā)生在獨(dú)立的異步線程N(yùn)acos內(nèi)部的定時(shí)任務(wù)線程每5秒執(zhí)行一次所以導(dǎo)致日志不斷增長(zhǎng)為什么Idea啟動(dòng)正常使用命令啟動(dòng)就有問題兩種運(yùn)行環(huán)境下的初始化時(shí)序、類加載機(jī)制或線程調(diào)度存在差異導(dǎo)致Nacos客戶端在IDEA中 “僥幸” 先于異步Feign調(diào)用完成初始化而打包后這種時(shí)序平衡被打破。IDEA運(yùn)行默認(rèn)會(huì)添加一些調(diào)試相關(guān)的JVM參數(shù)如-agentlib:jdwp用于調(diào)試這些參數(shù)可能會(huì)減慢JVM啟動(dòng)速度或禁用某些類加載 / 初始化優(yōu)化如提前編譯、類加載并行化知道問題所在和原因就可以很快解決這個(gè)問題我們可以等待所有Bean初始化完成后再去執(zhí)行對(duì)應(yīng)的方法這樣就不會(huì)出現(xiàn)容器未初始化完成的情況示例代碼如下RestControllerpublicclassAppController{ResourceprivateEchoServiceechoService;EventListener(ContextRefreshedEvent.class)// 指定監(jiān)聽的事件類型publicvoidhandleContextRefreshed(){echoService.echo(hello);System.out.println(容器就緒處理后續(xù)操作...);}}ContextRefreshedEvent是Spring框架中一個(gè)重要的容器事件當(dāng)Spring應(yīng)用上下文ApplicationContext完成初始化或刷新時(shí)觸發(fā)。它標(biāo)志著容器內(nèi)所有Bean已完成創(chuàng)建、依賴注入和初始化包括PostConstruct方法執(zhí)行完畢是監(jiān)聽容器初始化完成的核心事件。如果你的錯(cuò)誤并不是該問題導(dǎo)致的就需要考慮是否是依賴導(dǎo)致檢查依賴版本或者升級(jí)Nacos客戶端解決該問題。