為什麼不推薦現場注入?
一、概述
當我們在IDE中運行代碼分析工具時,它可能會針對帶有@Autowired
註解的字段發出“ Field injection is not recommended
”的警告。
在本教程中,我們將探討為什麼不推薦使用字段注入以及我們可以使用哪些替代方法。
2.依賴注入
對象使用它們的依賴對象而不需要定義或創建它們的過程稱為依賴注入。它是 Spring 框架的核心功能之一。
我們可以通過三種方式註入依賴對象,使用:
- 構造函數注入
- 二傳手注射
- 現場注入
這裡的第三種方法涉及使用@Autowired
註釋將依賴項直接注入到類中。儘管這可能是最簡單的方法,但我們必須了解它可能會導致潛在的問題。
更重要的是,即使是官方的 Spring 文檔也不再提供字段注入作為 DI 選項之一。
3.零安全
如果依賴項未正確初始化,字段注入會產生NullPointerException
風險。
讓我們定義EmailService
類並使用字段注入添加EmailValidator
依賴項:
@Service
public class EmailService {
@Autowired
private EmailValidator emailValidator;
}
現在,讓我們添加process()
方法:
public void process(String email) {
if(!emailValidator.isValid(email)){
throw new IllegalArgumentException(INVALID_EMAIL);
}
// ...
}
僅當我們提供EmailValidator
依賴項時, EmailService
才能正常工作。但是,使用字段注入,我們沒有提供直接的方法來實例化具有所需依賴項的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;
}
通過這種方法,我們公開了所需的依賴項。此外,我們現在要求客戶提供強制依賴項。換句話說,如果不提供EmailValidator
實例,就無法創建EmailService
的新實例。
4.不變性
使用字段注入,我們無法創建不可變類。
我們需要在聲明最終字段時或通過構造函數實例化它們。此外,一旦調用了構造函數,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. 測試
單元測試揭示了字段注入方法的主要缺點之一。
假設我們想編寫一個單元測試來檢查EmailService
中定義的process()
方法是否正常工作。
首先,我們想模擬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
註釋注入模擬。但是,如果字段注入策略失敗,Mockito 不會報告失敗。
另一方面,使用構造函數注入,我們可以在不反射的情況下提供所需的依賴項:
private EmailValidator emailValidator;
private EmailService emailService;
@BeforeEach
public void setup() {
this.emailValidator = Mockito.mock(EmailValidator.class);
this.emailService = new EmailService(emailValidator);
}
七、結論
在本文中,我們了解了不推薦使用字段注入的原因。
總而言之,我們可以對必需的依賴項使用構造函數注入,對可選的依賴項使用 setter 注入,而不是字段注入。
一如既往,本文的源代碼可在 GitHub 上獲得。