關(guān)于@Autowired注入依賴失敗的問題及解決
@Autowired注入依賴失敗的問題
1、現(xiàn)象描述
在Spring Boot項目中使用@Autowired注解,程序啟動時發(fā)現(xiàn)服務(wù)啟動失敗,提示:
Description:
Field metrics in com.be.fallback.servlet.FallbackServlet required a bean of type 'com.be.fallback.metrics.FallbackMetrics' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.be.fallback.metrics.FallbackMetrics' in your configuration.
2、問題分析
這里的錯誤原因很好分析。結(jié)合報錯信息及代碼,報錯處的代碼為FallbackMetrics注解了@Autowired進(jìn)行依賴注入,但是沒有找到可以被用來注入的實例。即Spring Boot獲取FallbackMetrics的實例失敗。
3、解決方案
根據(jù)分析,需要檢查可能導(dǎo)致Spring Boot依賴注入失敗的因素。
1.檢查掃描路徑。
掃描路徑是由@ComponentScan來指定的,默認(rèn)為標(biāo)注類當(dāng)前包及當(dāng)前包的子包。
也就是說,標(biāo)注了@ComponentScan的啟動類放在com.be.fallback包下面,只會掃描com.be.fallback包中的類,以及com.be.fallback.servlet、com.be.fallback.util等子包中的類,對于com.be.service等包中的類是不會掃描的。
- 注意事項一:很多人沒有使用@ComponentScan,但是使用了@SpringBootApplication。@SpringBootApplication是通過內(nèi)部封裝@ComponentScan注解來實現(xiàn)實例掃描的,所以使用@SpringBootApplication也是一樣的。
- 注意事項二:也可以通過為@ComponentScan或@SpringBootApplication注解指定參數(shù)來修改掃描路勁,示例:
// 如果使用@ComponentScan注解: @ComponentScan(basePackages = "com.be.fallback") ? // 如果使用@SpringBootApplication注解: @SpringBootApplication(scanBasePackages = "com.be.fallback")
2.檢查實例注冊。
檢查想要使用@Autowired注解自動注入依賴的類,是否標(biāo)注了用來注冊給Spring Boot的注解。這些注解包括@Component,@Service,@Repository,@Controller等。
3.其他問題。
如果上述步驟檢查完成,服務(wù)啟動又沒有產(chǎn)生其他異常,這時候基本上已經(jīng)排查代碼的問題。這時候需要檢查依賴、開發(fā)環(huán)境等是否有問題。檢查依賴需要了解自己需要哪些依賴,看是否配置齊全;檢查開發(fā)環(huán)境,可以通過將代碼拷貝到其他機(jī)器上執(zhí)行來判斷。
@Autowired依賴注入為啥不推薦了
這幾天更新升級了一下java編碼神器IDEA,升級完進(jìn)行日常開發(fā),可能是以前用的IDEA版本比較老舊,升級之后發(fā)現(xiàn)之前的日常寫法有了個warning提醒。來看圖:
如上圖,這就奇怪了,我們經(jīng)常寫的業(yè)務(wù)層就是service接口層和對應(yīng)的實現(xiàn)類層進(jìn)行屬性注入的時候都是采用注解進(jìn)行注入的。這也是springIOC給提供的比較方便的地方。我使用IDEA提供的自動修復(fù)提示修復(fù)了之后變成采用構(gòu)造函數(shù)的形式進(jìn)行注入了。
但是多年面向Spring開發(fā)的經(jīng)驗告訴我,使用@Autowired注解進(jìn)行依賴注入,肯定是沒有問題的。但是我的代碼潔癖不允許我這么不明不白的留一個警告在這里。所以,帶著我的潔癖,和我的好奇心,我開始研究起了這個警告。
警告內(nèi)容
我們簡單翻譯一下自動提示的是啥意思:
不建議直接在字段上進(jìn)行依賴注入。
Spring 開發(fā)團(tuán)隊建議:在Java Bean中永遠(yuǎn)使用構(gòu)造方法進(jìn)行依賴注入。對于必須的依賴,永遠(yuǎn)使用斷言來確認(rèn)。
我們說明上面的問題之前先回顧幾個spring相關(guān)的問題:
依賴注入的方式
Spring 有三種依賴注入的方式
1.基于屬性的注入
這種注入方式就是在bean的變量上使用注解進(jìn)行依賴注入。本質(zhì)上是通過反射的方式直接注入到field。這是我平常開發(fā)中看的最多也是最熟悉的一種方式。
@Autowired PushTaskService pushTaskService;
2.基于setter方法的注入
通過對應(yīng)變量的setXXX()方法以及在方法上面使用注解,來完成依賴注入。比如:
private static TaskGroupTemplateRepository taskGroupTemplateRepository; private static TaskGroupService taskGroupService; @Autowired public void setTaskGroupTemplateRepository(TaskGroupTemplateRepository taskGroupTemplateRepository,TaskGroupService taskGroupService){ ExcelListener2.taskGroupTemplateRepository = taskGroupTemplateRepository; ExcelListener2.taskGroupService = taskGroupService; }
說明:在 Spring 4.5 及更高的版本中,setXXX 上面的 @Autowired 注解是可以不寫的。
3.基于構(gòu)造方法的注入
將各個必需的依賴全部放在帶有注解構(gòu)造方法的參數(shù)中,并在構(gòu)造方法中完成對應(yīng)變量的初始化,這種方式,就是基于構(gòu)造方法的注入。比如:
@Autowired public ExcelListener(@Qualifier("taskGroupService") TaskGroupService taskGroupService) { this.taskGroupService = taskGroupService; }
@Autowired是干啥的
我們一般開發(fā)需要注入屬性的時候都會使用的這個注解@Autowired,跟這個注解類似的還有2個,@Resource, @Inject。Spring 支持使用@Autowired, @Resource, @Inject 三個注解進(jìn)行依賴注入。我們先看一下有啥區(qū)別:
@Autowired為Spring框架提供的注解,可以理解是Spring的親兒子。這里先給出一個示例代碼
public interface IndexService { void sayHello(); } @Service public class IndexServiceImpl implements IndexService { @Override public void sayHello() { System.out.println("hello, this is IndexServiceImpl"); } } @Service public class IndexServiceImpl2 implements IndexService { @Override public void sayHello() { System.out.println("hello, this is IndexServiceImpl2"); } }
測試方法
@SpringBootTest public class Stest { @Autowired // @Qualifier("indexServiceImpl2") IndexService indexService; @Test void gooo() { Assertions.assertNotNull(indexService); indexService.sayHello(); } }
按照type在上下文中查找匹配,查找type為IndexService的bean
- 如果有多個bean,則按照name進(jìn)行匹配
如果有@Qualifier注解,則按照@Qualifier指定的name進(jìn)行匹配,查找name為indexServiceImpl2的bean
如果沒有,則按照變量名進(jìn)行匹配。查找name為indexService的bean
- 匹配不到,則報錯。(@Autowired(required=false),如果設(shè)置required為false(默認(rèn)為true),則注入失敗時不會拋出異常)
@Inject是干啥的
在Spring 的環(huán)境下,@Inject和@Autowired 是相同的,因為它們的依賴注入都是使用AutowiredAnnotationBeanPostProcessor這個后置處理器來處理的。
這兩個的區(qū)別,首先@Inject是Java EE包里的,在SE環(huán)境需要單獨引入。另一個區(qū)別在于@Autowired可以設(shè)置required=false而@Inject并沒有這個屬性。也有的說@Inject是spring的干兒子。
@Resource是干啥的
@Resource是JSR-250定義的注解。Spring 在 CommonAnnotationBeanPostProcessor實現(xiàn)了對JSR-250的注解的處理,其中就包括@Resource。
這個@Resource有2個屬性name和type。在spring中name屬性定義為bean的名字,type這是bean的類型。
如果屬性上加@Resource注解那么他的注入流程是:
- 如果同時指定了name和type,則從Spring上下文中找到唯一匹配的bean進(jìn)行裝配,找不到則拋出異常。
- 如果指定了name,則從上下文中查找名稱匹配的bean進(jìn)行裝配,找不到則拋出異常。
- 如果指定了type,則從上下文中找到類型匹配的唯一bean進(jìn)行裝配,找不到或是找到多個,都會拋出異常。
- 如果既沒有指定name,又沒有指定type,則默認(rèn)按照byName方式進(jìn)行裝配;如果沒有匹配,按照byType進(jìn)行裝配。
上面的內(nèi)容都了解了之后我們接下來看為啥IDEA會有個warning提醒。IDEA 提示 Field injection is not recommended。warning提醒的注入方式就是第一種使用屬性注解的方式進(jìn)行注入。
屬性注入優(yōu)點
代碼看起來很簡單,通俗易懂。你的類可以專注于業(yè)務(wù)而不被依賴注入所污染。你只需要把@Autowired扔到變量之上就好了方便開發(fā)人員進(jìn)行代碼的編寫。
屬性注入可能出現(xiàn)的問題
- 問題1
基于 field 的注入,雖然不是絕對禁止使用,但是它可能會帶來一些隱含的問題。來我們舉個例子:
@Autowired private Person person; private String company; public UserServiceImpl(){ this.company = person.getCompany(); }
初看起來好像沒有什么問題,Person 類會被作為一個依賴被注入到當(dāng)前類中,同時這個類的 company 屬性將在初始化時通過person.getCompany() 方法來獲得值。我們嘗試運行一下就會發(fā)現(xiàn)報錯了。其實類似這個問題有人在stackoverflow上提問過,點我跳轉(zhuǎn)。
Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...]: Constructor threw exception; nested exception is java.lang.NullPointerException
在執(zhí)行this.company = person.getCompany();這段代碼的時候報了空指針。
出現(xiàn)這個問題的原因是,Java 在初始化一個類時,是按照靜態(tài)變量或靜態(tài)語句塊 –> 實例變量或初始化語句塊 –> 構(gòu)造方法 -> @Autowired 的順序。所以在執(zhí)行這個類的構(gòu)造方法時,person 對象尚未被注入,它的值還是 null。
- 問題2
使用這種基于 field 注入的方式,添加依賴是很簡單的,就算你的類中有十幾個依賴你可能都覺得沒有什么問題,如果你一個類注入非常多的其它的對象,擁有太多的依賴通常意味著你的類要承擔(dān)更多的責(zé)任,明顯違背了單一職責(zé)原則。順便我看了一下我們現(xiàn)在的業(yè)務(wù)代碼這個問題在我們的項目代碼中真的很常見。
- 問題3
這種注入形式就會造成你的類不能繞過反射(例如單元測試的時候)進(jìn)行實例化,必須通過依賴容器才能實例化。也就是類和依賴容器強(qiáng)耦合,不能在容器外使用。
spring建議
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
其實大概的意思就是2句話
- 強(qiáng)制依賴就用構(gòu)造器方式
- 可選、可變的依賴就用setter注入
spring對采用構(gòu)造方法注入的說明
Spring 團(tuán)隊提倡使用基于構(gòu)造方法的注入,因為這樣一方面可以將依賴注入到一個不可變的變量中 (注:final 修飾的變量),另一方面也可以保證這些變量的值不會是 null。此外,經(jīng)過構(gòu)造方法完成依賴注入的組件 (注:比如各個 service),在被調(diào)用時可以保證它們都完全準(zhǔn)備好了。與此同時,從代碼質(zhì)量的角度來看,一個巨大的構(gòu)造方法通常代表著出現(xiàn)了代碼結(jié)構(gòu)問題,這個類可能承擔(dān)了過多的責(zé)任。
spring對采用setter方法注入的說明
基于 setter 的注入,則只應(yīng)該被用于注入非必需的依賴,同時在類中應(yīng)該對這個依賴提供一個合理的默認(rèn)值。如果使用 setter 注入必需的依賴,那么將會有過多的 null 檢查充斥在代碼中。使用 setter 注入的一個優(yōu)點是,這個依賴可以很方便的被改變或者重新注入。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot+Stomp協(xié)議實現(xiàn)聊天功能
本示例實現(xiàn)一個功能,前端通過websocket發(fā)送消息給后端服務(wù),后端服務(wù)接收到該消息時,原樣將消息返回給前端,前端技術(shù)棧html+stomp.js,后端SpringBoot,需要的朋友可以參考下2024-02-02Java畢業(yè)設(shè)計實戰(zhàn)之養(yǎng)老院管理系統(tǒng)的實現(xiàn)
讀萬卷書不如行萬里路,只學(xué)書上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+JSP+Easyui+maven+mysql實現(xiàn)一個養(yǎng)老院管理系統(tǒng),大家可以在過程中查缺補漏,提升水平2022-03-03淺談Java回收對象的標(biāo)記和對象的二次標(biāo)記過程
這篇文章主要介紹了淺談Java回收對象的標(biāo)記和對象的二次標(biāo)記過程的相關(guān)內(nèi)容,小編覺得還是挺不錯的,這里給大家分享一下,需要的朋友可以參考。2017-10-10Java Spring的數(shù)據(jù)庫開發(fā)詳解
這篇文章主要介紹了Spring的數(shù)據(jù)庫開發(fā),主要圍繞SpringJDBC和Spring Jdbc Template兩個技術(shù)來講解,文中有詳細(xì)的代碼示例,需要的小伙伴可以參考一下2023-04-04關(guān)于如何搭建CAS服務(wù)并將CAS項目導(dǎo)入IDEA
這篇文章主要介紹了關(guān)于如何搭建CAS服務(wù)并將CAS項目導(dǎo)入IDEA的問題,文中提供了詳細(xì)的圖文講解,需要的朋友可以參考下,如果有錯誤的地方還請指正2023-03-03