spring依賴注入成功但在調(diào)用接口的時候拿到的依賴卻是null問題
前言
使用過spring的同學(xué)們都知道,如果出現(xiàn)了依賴注入失敗的問題首先會想到以下幾點:
1、對應(yīng)的類有沒有寫@Service或@Component注解以供能被spring掃描注冊;
2、在配置中有沒有配置要掃描的包路徑,或是對應(yīng)的類是否在已配置的包路徑下;
3、配置的包路徑錯誤導(dǎo)致掃描失敗;
4、同一個項目中jar沖突導(dǎo)致在注冊bean過程失敗進而導(dǎo)致注入不成功;
問題描述
使用springboot的時候發(fā)現(xiàn)項目正常編譯運行,所有的bean都被掃描加載了,但是在前端調(diào)用接口的時候發(fā)現(xiàn)controller里的service竟然是null。
具體如下圖所示:
由上圖可以看出Device對應(yīng)的那個bean在spring容器中已經(jīng)是被注冊過了的,就意味著并不是對應(yīng)的類沒有被加載掃描到。
但我通過在測試類中運行發(fā)現(xiàn)使用同樣的注入方式是可以注入成功并成功運行,如下圖所示:
注入測試
為了進一步測試是否能正常的注入依賴,將原先的注入方式改成了構(gòu)造注入的方式,發(fā)現(xiàn)也是能夠正常的注入進去,如下圖所示:
通過以上測試發(fā)現(xiàn),這次的問題并不是因為掃描路徑或是注解的問題導(dǎo)致的,當(dāng)然這里的jar依賴這些也是正常的。
分析
仔細(xì)回想下這次的問題,項目服務(wù)能正常啟動,在spring的bean容器中也能找到對應(yīng)的bean那就說明bean注冊過程是沒有問題的。
接下來通過注入測試發(fā)現(xiàn)這幾個service也是能夠正常的注入到controller里,那說明項目中DI這個過程也是OK的,否則的話不可能是換一種注入方式就能注入。
由此可見,此問題并不是因為spring的問題導(dǎo)致。接下來本菜鳥又查詢了一些資料說可能是因為動態(tài)代理的問題導(dǎo)致,項目中沒有獨特申明代理方式所以默認(rèn)使用的是JDK的動態(tài)代理。
轉(zhuǎn)眼一想感覺是發(fā)現(xiàn)什么了(此處省略一萬個滑稽…),感覺離成功又更近一步了。
這里我們先來看下常用的動態(tài)代理:
1、jdk動態(tài)代理:
- 使用jdk動態(tài)代理只能對實現(xiàn)了接口的類生成代理,而不能針對類;
- 使用反射生成代理類;
2、cglib動態(tài)代理:
- 使用cglib代理是針對類實現(xiàn)代理,主要是對指定的類生成一個子類,覆蓋其中的方法(繼承);
- 底層采用ASM字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,比使用Java反射效率要高。
了解到這里后本菜感覺想到點什么了,本菜的項目中沒有單獨設(shè)置代理模式,所以spring對bean處理的時候默認(rèn)是使用了jdk動態(tài)代理,而項目里的service并非是接口而是一個class,而且還未實現(xiàn)某個接口,只是單純的class
?
本菜就想,是不是因為接口是單純的類沒有實現(xiàn)接口,而jdk的動態(tài)代理沒有將這個類代理到,所以導(dǎo)致注入失敗了,于是本菜就設(shè)置了下代理為cglib動態(tài)代理:
但經(jīng)本菜鳥測試發(fā)現(xiàn),問題依舊存在…(內(nèi)心開始MMP了)。這時本菜鳥又想起了另一個點,spring在對bean進行代理的時候會自行選擇代理方式:
(1)當(dāng)Bean實現(xiàn)接口時,Spring就會用JDK的動態(tài)代理;
(2)當(dāng)Bean沒有實現(xiàn)接口時,Spring就使用CGlib動態(tài)代理;
所以說再次申明代理方式也是沒有用的。
除了以上的一些疑點,本菜又發(fā)現(xiàn)出現(xiàn)問題的代碼里使用了spring的AOP,
于是開始懷疑是不是AOP搞鬼了,于是找了相關(guān)代碼
從這里可見這里AOP切面切點用在了調(diào)用controller方法前,進而再聯(lián)想到項目目前的代理方式是cglib動態(tài)代理而非jdk動態(tài)代理。于是猜想是不是因為AOP切入的時候代理未生效沒有將對應(yīng)的bean給到對應(yīng)的方法里,后面繼續(xù)深入查詢看看@Aspect使用的代理方式
鎖定這個
繼續(xù)深入,看到這里發(fā)現(xiàn)看到有ProxyCreator (代理創(chuàng)建)
判斷生成的bean是否能夠被代理
創(chuàng)建代理類bean實例
代碼較多省略了部分,這里繼續(xù)走到獲取代理
這里我們使用的是cglib動態(tài)代理
關(guān)鍵代碼判斷對應(yīng)bean是否需要被代理并返回實例
設(shè)置過濾
中間的代碼比較多,省略了部分,關(guān)鍵代碼
這里可以看到對映射的方法進行了過濾,依次看過去發(fā)現(xiàn)這里存在疑慮
看到這里就有疑問了,因為使用AOP的時候?qū)υL問修飾符是沒有啥影響的,public、private、protect都是可以的,所以最終問題應(yīng)該是這里在使用cglib的時候把私有方法過濾掉了不被代理,所以導(dǎo)致了在controller中依賴注入是成功的但調(diào)用這個接口方法時拿到的依賴為null,回看項目源代碼:
解決問題
把這里的private改成public就可以了…
反思
論寫代碼仔細(xì)的重要性?。?!雖然此次的事故代碼不是出自我手,但是這個小小的問題卻耗費了我好久時間,這里讓我不得不反思下以后寫代碼要仔細(xì)仔細(xì)再仔細(xì),同時遇到bug的時候在排查問題的時候也是要仔細(xì)仔細(xì)仔細(xì)!不然很簡單的問題也是會被忽略掉。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot項目集成xxljob實現(xiàn)全紀(jì)錄
XXL-JOB是一個分布式任務(wù)調(diào)度平臺,本文主要介紹了SpringBoot項目集成xxljob實現(xiàn)全紀(jì)錄,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11IDEA集成DeepSeek通過離線安裝解決無法安裝Proxy?AI插件問題(最新推薦)
許多開發(fā)者嘗試通過安裝Proxy?AI等插件將AI能力引入IDEA,但在實際使用中常遭遇插件安裝失敗、網(wǎng)絡(luò)連接不穩(wěn)定或兼容性沖突等問題,本文給大家介紹IDEA集成DeepSeek通過離線安裝解決無法安裝Proxy?AI插件問題,感興趣的朋友一起看看吧2019-12-12SpringBoot redis分布式緩存實現(xiàn)過程解析
這篇文章主要介紹了SpringBoot redis分布式緩存實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-10-10java?ResourceBundle讀取properties文件方式
這篇文章主要介紹了java?ResourceBundle讀取properties文件方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08springboot 使用yml配置文件自定義屬性的操作代碼
在SpringBoot中yml/yaml文件可以自定義一些屬性,以供注入給自定義bean對象的屬性,主要通過空格和層次來實現(xiàn),類似于python代碼,本文通過實例代碼給大家介紹springboot 使用yml配置文件自定義屬性,感興趣的朋友跟隨小編一起看看吧2024-03-03