1. 概述
當我們在 IDE 中運行代碼分析工具時,可能會針對帶有 @Autowired 註解的字段發出“字段注入不推薦”警告。
在本教程中,我們將探討為什麼不推薦字段注入以及我們可以使用哪些替代方法。
2. 依賴注入
依賴注入是指對象使用其依賴對象,而無需定義或創建這些依賴對象的過程。它是 Spring 框架的核心功能之一。
我們可以通過三種方式注入依賴對象,即:
- 構造注入
- 設置注入
- 字段注入
這裏的第三種方法涉及使用 <em @Autowired</em> 註解直接將依賴注入到類中。雖然它可能是最簡單的做法,但我們必須理解它可能導致的問題。
此外,即使官方 Spring 文檔 也 不再將字段注入作為 DI 選項之一。
3. 零值安全性
字段注入會導致如果依賴項未正確初始化,則存在產生 的風險。
讓我們定義 類並使用字段注入添加 依賴項:
@Service
public class EmailService {
@Autowired
private EmailValidator emailValidator;
}現在,讓我們添加 process() 方法:
public void process(String email) {
if(!emailValidator.isValid(email)){
throw new IllegalArgumentException(INVALID_EMAIL);
}
// ...
}EmailService 只有在提供EmailValidator依賴時才能正常工作。 但是,使用字段注入,我們沒有提供直接實例化EmailService並指定所需依賴的方式。
此外,我們可以使用默認構造函數創建EmailService實例:
EmailService emailService = new EmailService();
emailService.process("[email protected]");執行上述代碼會導致 NullPointerException,因為我們沒有提供其必需的依賴項,即 EmailValidator。
現在,我們可以使用構造器注入來 降低 NullPointerException 的風險:
private final EmailValidator emailValidator;
public EmailService(final EmailValidator emailValidator) {
this.emailValidator = emailValidator;
}採用這種方法,我們公開暴露了所需的依賴項。此外,我們現在要求客户端提供必需的依賴項。換句話説,無法創建新的 EmailService 實例,除非提供 EmailValidator 實例。
4. 不可變性 (Immutability)
通過字段注入,我們無法創建不可變類。
在聲明字段或通過構造函數時,需要實例化最終字段。 此外,Spring 在構造函數被調用後僅執行一次自動注入。因此,無法使用字段注入方式自動注入最終字段。
由於依賴是可變的,因此無法確保它們在初始化後保持不變。 此外,重新分配非最終字段會導致應用程序運行時產生意外副作用。
相反,我們可以使用構造注入強制依賴項,並使用 setter 注入進行可選依賴項。 這樣,我們就可以確保所需的依賴項保持不變。
5. 設計問題
現在,我們來討論一下在字段注入方面可能遇到的設計問題。
5.1. 單一職責原則違規
單一職責原則是 SOLID 原則之一,它指出每個類應該只負責一項任務。換句話説,一個類應該只對一項操作負責,因此只應該有一次變更的原因。
當我們使用字段注入時,我們可能會違反單一職責原則。 我們可以輕鬆地添加不必要的依賴項,並創建一個執行多項任務的類。
另一方面,如果我們使用構造器注入,我們會注意到如果構造器包含多個依賴項,就會出現設計問題。 此外,IDE 甚至會在構造器中包含超過七個參數時發出警告。
5.2. 循環依賴
簡單來説,循環依賴指的是兩個或多個類相互依賴的情況。由於這些依賴關係,無法構造對象,並且執行過程可能導致運行時錯誤或無限循環。
使用字段注入可能會導致循環依賴不被察覺:
@Component
public class DependencyA {
@Autowired
private DependencyB dependencyB;
}
@Component
public class DependencyB {
@Autowired
private DependencyA dependencyA;
}由於依賴在需要時注入,而不是在上下文加載時,Spring 不會拋出 BeanCurrentlyInCreationException。
使用構造器注入,可以在編譯時檢測循環依賴,因為它們會創建無法解析的錯誤。
此外,如果我們的代碼中存在循環依賴,這可能表明我們的設計存在問題。因此,如果可能,我們應該考慮重新設計我們的應用程序。
但是,由於 Spring Boot 2.6 版本 默認情況下,循環依賴不再允許。
6. 測試
單元測試揭示了使用字段注入方法的主要缺點。
假設我們想編寫一個單元測試,以檢查 process() 方法在 EmailService 中是否正常工作。
首先,我們希望模擬 EmailValidation 對象。但是,由於我們使用字段注入插入了 EmailValidator,因此我們無法直接用一個模擬的版本替換它:
EmailValidator validator = Mockito.mock(EmailValidator.class);
EmailService emailService = new EmailService();此外,在 EmailService 類中提供 setter 方法還會引入額外的漏洞,因為除了測試類之外的其他類也可以調用該方法。
但是,我們可以通過反射實例化我們的類。例如,我們可以使用 Mockito:
@Mock
private EmailValidator emailValidator;
@InjectMocks
private EmailService emailService;
@BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
}在這裏,Mockito 將嘗試使用 @InjectMocks 註解注入 Mock 對象。 但是,如果字段注入策略失敗,Mockito 不會報告失敗。
另一方面,使用構造函數注入,我們可以提供所需的依賴項,而無需使用反射:
private EmailValidator emailValidator;
private EmailService emailService;
@BeforeEach
public void setup() {
this.emailValidator = Mockito.mock(EmailValidator.class);
this.emailService = new EmailService(emailValidator);
}7. 結論
在本文中,我們瞭解到為什麼字段注入不被推薦。
總結一下,相比於字段注入,我們可以使用構造器注入來處理必需依賴項,使用設置者注入來處理可選依賴項。