1. 概述
本教程將介紹 IoC(控制反轉)和 DI(依賴注入)的概念,並探討這些概念在 Spring 框架中的實現方式。
2. 控制反轉是什麼?
控制反轉是軟件工程中的一個原則,它將對象的控制權或程序的某些部分轉移給容器或框架。我們通常在面向對象編程的背景下使用它。與傳統的編程方式不同,在傳統的編程中,我們的自定義代碼會調用庫,IoC 允許框架控制程序的流程並調用我們的自定義代碼。為了實現這一點,框架使用帶有內置額外行為的抽象。 如果我們想要添加自己的行為,則需要擴展框架的類或插件自己的類。
這種架構的優點是:
- 將任務的執行與它的實現解耦
- 使其更容易在不同的實現之間切換
- 程序的模塊化程度更高
- 更容易通過隔離組件或模擬其依賴項來測試程序,並允許組件通過合同進行通信
我們可以通過諸如策略設計模式、服務定位器模式、工廠模式和依賴注入(DI)等機制來實現控制反轉。
接下來,我們將研究 DI。
3. 什麼是依賴注入?
依賴注入是一種我們可以用來實現 IoC 的模式,其中控制權被反轉時,會設置對象的依賴項。通過其他對象與對象建立連接,或者“注入”對象到其他對象中,是由彙編器完成的,而不是由對象本身完成。
在傳統編程中,我們創建對象依賴的方式如下所示:
public class Store {
private Item item;
public Store() {
item = new ItemImpl1();
}
}在上面的示例中,我們需要在 Store 類本身中實例化 Item 接口的實現。
通過使用依賴注入(DI),我們可以重寫示例,而無需指定我們想要使用的 Item 的實現。
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}在接下來的部分,我們將探討如何通過元數據提供 Item 的實現。
依賴注入 (IoC) 和控制反轉 (DI) 都是相對簡單的概念,但它們對我們系統架構有着深遠的影響,因此完全理解它們非常值得。
4. Spring IoC 容器
IoC 容器是實現 IoC 的框架的常見特徵。
在 Spring 框架中,接口 ApplicationContext 代表 IoC 容器。 Spring 容器負責實例化、配置和組裝稱為 bean 的對象,以及管理它們的生命週期。
Spring 框架提供了多個 ApplicationContext 接口的實現:AnnotationConfigApplicationContext、ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 用於獨立應用程序,以及 WebApplicationContext 用於 Web 應用程序。
為了組裝 bean,容器使用配置元數據,該元數據可以是 XML 配置或註解的形式。
以下是手動實例化容器的一種方式:
ApplicationContext context
= new ClassPathXmlApplicationContext("applicationContext.xml");以下是翻譯後的內容:
以下是一個手動實例化容器的示例,使用了 AnnotationConfigApplicationContext:
AnnotationConfigApplicationContext annotationContext = new AnnotationConfigApplicationContext();當您創建 AnnotationConfigApplicationContext 實例並提供一個或多個配置類時,它會掃描這些類以查找 @Bean 註解和其他相關注解。然後,它會初始化和管理這些類中定義的 Bean,設置它們的依賴關係並管理它們的生命週期。您可以在這裏找到詳細示例。
要設置上文示例中的 item 屬性,可以使用元數據。然後容器將讀取此元數據並使用它在運行時組裝 Bean。
在 Spring 中,依賴注入可以通過構造函數、設置器或字段進行。
5. 基於構造函數的依賴注入
在基於構造函數的依賴注入的方案中,容器將通過調用構造函數,並傳入代表我們想要設置的依賴項的參數來完成。Spring 主要通過類型,然後是屬性名稱,最後是索引來解決每個參數,以消除歧義。下面我們通過註解來配置一個 Bean 以及它的依賴項:
@Configuration
public class AppConfig {
@Bean
public Item item1() {
return new ItemImpl1();
}
@Bean
public Store store() {
return new Store(item1());
}
}@Configuration 註解表明該類是 Bean 定義的來源。我們也可以將其添加到多個配置類中。
我們使用@Bean註解在一個方法上定義一個 Bean。如果未指定自定義名稱,則 Bean 名稱將默認為方法名稱。
對於具有默認singleton作用域的 Bean,Spring 首先檢查 Bean 緩存中是否已存在實例,並在不存在時才創建新的實例。如果使用prototype作用域,則容器在每次方法調用時返回一個新的 Bean 實例。
通過 XML 配置創建 Bean 的另一種方式是:
<bean id="item1" class="org.baeldung.store.ItemImpl1" />
<bean id="store" class="org.baeldung.store.Store">
<constructor-arg type="ItemImpl1" index="0" name="item" ref="item1" />
</bean>6. 基於設置器依賴注入
對於基於設置器依賴注入,容器會在調用無參數構造函數或無參數靜態工廠方法實例化 Bean 後,調用我們 Bean 的設置器方法。 讓我們使用註解來配置這個:
@Bean
public Store store() {
Store store = new Store();
store.setItem(item1());
return store;
}我們還可以使用XML來實現相同的Bean配置:
<bean id="store" class="org.baeldung.store.Store">
<property name="item" ref="item1" />
</bean>我們可以將基於構造器和基於設置器的注入方式應用於同一個 Bean。Spring 文檔建議,對於強制依賴,應使用基於構造器的注入;對於可選依賴,則應使用基於設置器的注入。
7. 基於字段的依賴注入
在基於字段的依賴注入的情況下,我們可以通過使用 @Autowired 註解來標記依賴項進行注入:
public class Store {
@Autowired
private Item item;
}在構造 Store 對象時,如果不存在構造函數或 setter 方法用於注入 Item Bean,容器將使用反射機制將 Item 注入到 Store 中。
我們也可以使用 XML 配置來實現這一點。
這種方法可能看起來更簡單、更清晰,但我們不建議使用它,因為它存在一些缺點,例如:
- 該方法使用反射注入依賴,比基於構造函數或 setter 方法的注入更昂貴。
- 使用這種方法很容易添加多個依賴。如果我們使用基於構造函數注入,具有多個參數會讓我們認為該類執行了不止一件事情,這可能會違反單一職責原則。
有關 @Autowired 註解的更多信息,請參閲“在 Spring 中進行 Wiring”文章。
8. 自動注入依賴項
注入允許 Spring 容器通過檢查已定義的豆(bean)來自動解決協作豆之間的依賴關係。
以下是使用 XML 配置自動注入豆的四種模式:
- 無: 默認值 – 這意味着不使用任何自動注入,並且必須顯式指定依賴項。
- ByName: 自動注入基於屬性名稱進行,因此 Spring 將查找與需要設置的屬性名稱相同的豆。
- ByType: 類似於 ByName 自動注入,僅基於屬性類型。這意味着 Spring 將查找具有與屬性類型相同的豆進行設置。如果存在多個具有相同類型的豆,則框架會拋出異常。
- Constructor: 自動注入基於構造函數參數進行,這意味着 Spring 將查找具有與構造函數參數相同類型的豆。
例如,讓我們使用類型將上述 item 豆自動注入到 store 豆中:
@Bean(autowire = Autowire.BY_TYPE)
public class Store {
private Item item;
public setItem(Item item) {
this.item = item;
}
}請注意,autowrie 屬性在 Spring 5.1 版本及更高版本中已棄用。
我們還可以使用 @Autowired 註解通過類型進行自動注入:
public class Store {
@Autowired
private Item item;
}如果存在同類型的多個 Bean,我們可以使用 @Qualifier 註解通過名稱引用 Bean:
public class Store {
@Autowired
@Qualifier("item1")
private Item item;
}現在讓我們通過 XML 配置來通過類型自動注入 Bean:
<bean id="store" class="org.baeldung.store.Store" autowire="byType"> </bean>接下來,我們通過 XML 將名為 item 的 Bean 注入到 store Bean 的 item 屬性中,通過名稱:
<bean id="item" class="org.baeldung.store.ItemImpl1" />
<bean id="store" class="org.baeldung.store.Store" autowire="byName">
</bean>我們還可以通過構造函數參數或設置器來明確定義依賴項,從而覆蓋自動注入。
9. 延遲初始化 Bean
默認情況下,容器會在初始化時創建並配置所有單例 Bean。為了避免這種情況,可以使用 <em >lazy-init</em> 屬性,並將值設置為 <em >true</em>,在 Bean 的配置上:
<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />因此,item1 Bean 只會在首次請求時進行初始化,而不會在應用程序啓動時進行初始化。 這種方法的優勢在於初始化時間更快,但代價是,直到 Bean 被請求後,我們才能夠發現任何配置錯誤,這可能在應用程序已經運行數小時甚至數天之後才發生。
10. 結論
在本文中,我們介紹了控制反轉和依賴注入的概念,並在 Spring 框架中進行了示例説明。
您可以在 Martin Fowler 的文章中瞭解更多關於這些概念的信息:
此外,我們可以在 Spring 框架參考文檔 中學習 Spring 中 IoC 和 DI 的實現。