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