請(qǐng)不要再使用@Autowired/@Resource注解進(jìn)行字段注入
1. 簡(jiǎn)介
對(duì)象在無需定義或創(chuàng)建其依賴對(duì)象的情況下使用這些依賴對(duì)象的過程被稱為依賴注入。這是Spring框架的核心功能之一。
我們可以通過三種方式注入依賴對(duì)象:
- 構(gòu)造函數(shù)注入
- Setter注入
- 字段注入
而這里的字段注入是使用 @Autowired 注解將依賴關(guān)系直接注入類中。雖然這可能是最簡(jiǎn)單的方法,但我們必須明白它可能會(huì)導(dǎo)致一些潛在的問題。而且,甚至Spring的官方文檔也不再將字段注入列為依賴注入的選項(xiàng)之一。如下官方文檔:
圖片
2. 引發(fā)的問題
2.1 空安全(Null-Safety)
如果未正確初始化依賴關(guān)系,字段注入會(huì)產(chǎn)生 NullPointerException 風(fēng)險(xiǎn)。如下示例:
@Repository
public class PersonDAO {
}
@Service
public class PersonService {
// @Autowired
@Resource
private PersonDAO dao ;
public String toString() {
return "PersonService [dao=" + dao + "]";
}
}
在上面的示例中,只有容器中存在 PersonDAO 依賴項(xiàng),PersonService才能正常工作。然而,在使用字段注入時(shí),我們沒有提供直接實(shí)例化 PersonService 所需的依賴關(guān)系的方法。
使用@Autowired時(shí),啟動(dòng)容器會(huì)報(bào)錯(cuò),這也還算好,如果我們?nèi)缦率褂茫?/p>
@Autowired(required = false)
容器啟動(dòng)將正常,但是如果當(dāng)調(diào)用方法時(shí)將出現(xiàn)NPE。這里如果你使用@Resource也會(huì)出現(xiàn)NPE問題。
此外,我們還可以使用默認(rèn)構(gòu)造函數(shù)創(chuàng)建 PersonService 實(shí)例:
PersonService ps = new PersonService() ;
ps.xxx() ;
這都將導(dǎo)致NPE問題。
對(duì)于這NPE問題,我們可以通過構(gòu)造函數(shù)注入來降低NPE問題。
private final PersonDAO dao ;
public PersonService(PersonDAO dao) {
this.dao = dao ;
}
通過這種方法,我們公開了所需的依賴關(guān)系。此外,我們現(xiàn)在要求客戶提供必須的依賴項(xiàng)。換句話說,如果不提供 PersonDAO 實(shí)例,就無法創(chuàng)建新的 PersonService 實(shí)例。
2.2 不變性(Immutability)
使用字段注入,我們無法創(chuàng)建不可變類。
我們需要在聲明或通過構(gòu)造函數(shù)實(shí)例化最終字段。此外,一旦構(gòu)造函數(shù)被調(diào)用,Spring 就會(huì)執(zhí)行自動(dòng)裝配。因此,不可能使用字段注入方式來裝配最終字段。
由于依賴關(guān)系是可變的,我們無法確保它們?cè)诔跏蓟蟊3植蛔?。此外,重新分配非最終字段可能會(huì)在運(yùn)行應(yīng)用程序時(shí)產(chǎn)生意想不到的副作用?;蛘撸覀兛梢詫?duì)強(qiáng)制依賴關(guān)系使用構(gòu)造器注入,對(duì)可選依賴關(guān)系使用setter注入。這樣,我們就能確保所需的依賴關(guān)系保持不變。
2.3 設(shè)計(jì)問題
違反單一職責(zé)
作為SOLID原則的一部分,單一職責(zé)原則規(guī)定每個(gè)類應(yīng)該只有一項(xiàng)職責(zé)。換句話說,一個(gè)類應(yīng)該只負(fù)責(zé)一個(gè)操作,因此只有一個(gè)改變的理由。
當(dāng)我們使用字段注入時(shí),最終可能會(huì)違反單一責(zé)任原則。我們很容易添加超出必要的依賴關(guān)系,并創(chuàng)建一個(gè)身兼多職的類。
另一方面,如果我們使用構(gòu)造函數(shù)注入,我們就會(huì)發(fā)現(xiàn),如果一個(gè)構(gòu)造函數(shù)有超過幾個(gè)依賴關(guān)系,我們可能會(huì)遇到設(shè)計(jì)問題。此外,如果構(gòu)造函數(shù)中的參數(shù)超過 7 個(gè),甚至集成開發(fā)環(huán)境也會(huì)發(fā)出警告。
循環(huán)依賴問題
簡(jiǎn)單地說,當(dāng)兩個(gè)或多個(gè)類相互依賴時(shí),就會(huì)出現(xiàn)循環(huán)依賴。由于存在這些依賴關(guān)系,因此無法構(gòu)造對(duì)象,執(zhí)行過程中可能會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤或無限循環(huán)。
使用字段注入可能會(huì)導(dǎo)致循環(huán)依賴被忽視,如下示例:
@Component
public class A {
@Autowired
private B b ;
}
@Component
public class B {
@Autowired
private A a ;
}
這里兩個(gè)類通過字段注入,形成循環(huán)依賴。這樣運(yùn)行不會(huì)有問題也就是并不會(huì)拋出BeanCurrentlyInCreationException 異常;簡(jiǎn)單說,對(duì)于這種循環(huán)依賴其實(shí)是一種設(shè)計(jì)的缺陷我們應(yīng)該避免,但是這里并沒有表現(xiàn)出來。
注意:在Spring環(huán)境下默認(rèn)是允許循環(huán)依賴的。
但是在Spring Boot環(huán)境并且從2.6版本開始循環(huán)依賴默認(rèn)是不允許的,也就是上面的字段注入將拋出異常。
乘熱打鐵,接下來說說循環(huán)依賴的解決。
如果你將上面的依賴注入方式改成了構(gòu)造函數(shù),如下:
@Component
public class A {
private B b ;
public A(B b) {
this.b = b ;
}
}
@Component
public class B {
private A a ;
public B(A a) {
this.a = a ;
}
}
容器啟動(dòng)將拋出BeanCurrentlyInCreationException 異常;對(duì)于這種構(gòu)造函數(shù)的循環(huán)依賴是可以通過@Lazy解決。如下示例:
// 構(gòu)造函數(shù)上添加注解或者是在參數(shù)加都可以
// @Lazy
public A(@Lazy B b) {
this.b = b ;
}
只要在任意一端添加了@Lazy都可解決。
最后,我們可以使用構(gòu)造器注入來代替字段注入,構(gòu)造器注入是必需的,而setter注入則是可選的。