在本文中,我們將介紹 IoC(控制反轉)和 DI(依賴註入)的概念,以及如何在 Spring 框架中實現它們。 什麼是控制反轉? 控制反轉是軟體工程中的一個原則,它將對象或程式的某些部分的控制權轉移給容器或框架。我們最常在面向對象編程的上下文中使用它。 與傳統編程相比,傳統編程中我們的自定義代碼調用 ...
在本文中,我們將介紹
IoC
(控制反轉)和DI
(依賴註入)的概念,以及如何在Spring
框架中實現它們。
什麼是控制反轉?
控制反轉是軟體工程中的一個原則,它將對象或程式的某些部分的控制權轉移給容器或框架。我們最常在面向對象編程的上下文中使用它。
與傳統編程相比,傳統編程中我們的自定義代碼調用庫,而 IoC 使框架控製程序的流程並調用我們的自定義代碼。為了實現這一點,框架使用具有附加行為的抽象。如果我們想要添加自己的行為,我們需要擴展框架的類或插入自己的類。
這種架構的優點是:
- 將任務的執行與其實現分離
- 更容易在不同實現之間切換
- 程式的更高的模塊化
- 更容易通過隔離組件或模擬其依賴項來測試程式,並允許組件通過契約進行通信
我們可以通過各種機制實現 IoC
,例如:策略設計模式、服務定位器模式、工廠模式和依賴註入(DI
)。
什麼是依賴註入?
依賴註入是一種我們可以用來實現 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
都是簡單的概念,但它們對我們構建系統的方式有深刻的影響,因此值得充分理解。
Spring IoC容器
IoC
容器是實現 IoC
的框架的常見特征。
在 Spring
框架中,介面 ApplicationContext
表示 IoC
容器。Spring
容器負責實例化、配置和組裝稱為 bean
的對象,以及管理它們的生命周期。
Spring
框架提供了 ApplicationContext
介面的幾個實現:ClassPathXmlApplicationContext
和 FileSystemXmlApplicationContext
用於獨立應用程式,以及 WebApplicationContext
用於 Web 應用程式。
為了組裝 bean
,容器使用配置元數據,可以是 XML
配置或註釋形式。
以下是手動實例化容器的一種方法:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
在上面的示例中,我們可以使用元數據設置 item
屬性,然後容器將讀取此元數據併在運行時使用它來組裝 bean
。
在 Spring
中,可以通過構造函數、setter
或欄位來進行依賴註入。
基於構造函數的依賴註入
在基於構造函數的依賴註入的情況下,容器將調用具有表示我們要設置的依賴項的參數的構造函數。
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
實例。
創建 bean
的另一種方式是通過 XML
配置:
<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>
## 基於setter的依賴註入
對於基於 `setter` 的 `DI`,容器將在調用沒有參數的構造函數或沒有參數的靜態工廠方法來實例化 `bean` 之後調用我們類的 `setter` 方法。讓我們使用註釋創建此配置:
```java
@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>
我們可以將構造函數和 setter
類型的註入結合在同一個 bean
中。Spring
文檔建議將基於構造函數的註入用於必需的依賴項,將基於 setter
的註入用於可選的依賴項。
基於欄位的依賴註入
在基於欄位的 DI
的情況下,我們可以通過帶有 @Autowired
註釋的註釋將依賴項註入其中:
public class Store {
@Autowired
private Item item;
}
在構造 Store
對象時,如果沒有構造函數或 setter
方法將 Item bean
註入其中,容器將使用反射將 Item
註入 Store
中。
我們也可以使用 XML 來實現這一點。
這種方法可能看起來更簡單、更清晰,但我們不建議使用它,因為它有一些缺點,例如:
- 此方法使用反射來註入依賴項,這比基於構造函數或
setter
的註入更昂貴。 - 使用此方法很容易添加多個依賴項。如果我們使用構造函數註入,有多個參數會讓我們認為這個類做了不止一件事,這可能違反單一責任原則。
自動裝配依賴項
自動裝配允許 Spring
容器通過檢查已定義的 bean
來自動解決協作 bean
之間的依賴關係。
使用 XML
配置有四種自動裝配 bean
的模式:
no
:預設值 - 這意味著不使用自動裝配,我們必須顯式地命名依賴項。byName
:按屬性名稱進行自動裝配,因此Spring
將查找與需要設置的屬性同名的bean
。byType
:類似於按名稱進行自動裝配,僅基於屬性的類型。這意味著Spring
將查找具有相同類型的屬性來設置的bean
。如果有多個bean
具有該類型,則框架會拋出異常。constructor
:基於構造函數參數進行自動裝配,意味著Spring
將查找具有與構造函數參數相同類型的bean
。
例如,讓我們通過類型創建具有依賴項 item
的 store
bean
。
public class AppConfig {
@Bean
public Item item() {
return new ItemImpl1();
}
@Bean(autowire = Autowire.BY_TYPE)
public Store store() {
return new Store();
}
}
請註意,自 Spring 5.1
起,autowire
屬性已棄用。
我們還可以使用 @Autowired
註釋按類型註入 bean
:
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>
我們還可以通過構造函數參數或 setter
顯式定義依賴關係來覆蓋自動裝配。
惰性初始化的bean
預設情況下,容器在初始化期間創建和配置所有單例 bean
。為了避免這種情況,我們可以在 bean
配置上使用值為 true
的 lazy-init
屬性:
<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />
因此,只有在第一次請求它時,才會初始化 item1
bean
,而不是在啟動時。這樣做的優點是初始化時間更快,但缺點是我們在 bean
被請求之後才會發現任何配置錯誤,這可能是應用程式已運行數小時甚至數天之後。
結論
在本文中,我們介紹了控制反轉和依賴註入的概念,併在 Spring
框架中進行了示例。
最後
為了方便其他設備和平臺的小伙伴觀看往期文章:
微信公眾號搜索:Let us Coding
,關註後即可獲取最新文章推送
看完如果覺得有幫助,歡迎 點贊、收藏、關註