概述
在本文中,我們將介紹IoC(控制反轉)和DI(依賴注入)的概念,以及如何在Spring框架中實現它們。
什么是控制反轉?
控制反轉是軟件工程中的一個原則,它將對象或程序的某些部分的控制權轉移給容器或框架。我們最常在面向對象編程的上下文中使用它。
與傳統編程相比,傳統編程中我們的自定義代碼調用庫,而IoC使框架控制程序的流程并調用我們的自定義代碼。為了實現這一點,框架使用具有附加行為的抽象。如果我們想要添加自己的行為,我們需要擴展框架的類或插入自己的類。
這種架構的優點是:
- 將任務的執行與其實現分離
- 更容易在不同實現之間切換
- 程序的更高的模塊化
- 更容易通過隔離組件或模擬其依賴項來測試程序,并允許組件通過契約進行通信
我們可以通過各種機制實現IoC,例如:策略設計模式、服務定位器模式、工廠模式和依賴注入(DI)。
什么是依賴注入?
依賴注入是一種我們可以用來實現IoC的模式,其中被反轉的控制是設置對象的依賴項。
將對象與其他對象連接或將對象“注入”到其他對象中是由匯編程序而不是對象本身完成的。
下面是在傳統編程中創建對象依賴關系的方法:
ini復制代碼publicclassStore {
private Item item;
publicStore() {
item =newItemImpl1();
}
}Copy
在上面的示例中,我們需要在*Store
類本身中實例化
Item*接口的實現。
通過使用DI,我們可以重寫該示例,而不指定我們想要的*Item*的實現:
ini復制代碼publicclassStore {
private Item item;
publicStore(Item item) {
this.item = item;
}
}Copy
在接下來的幾節中,我們將看看如何通過元數據提供
Item
的實現。
IoC和DI都是簡單的概念,但它們對我們構建系統的方式有深刻的影響,因此值得充分理解。
Spring IoC容器
IoC容器是實現IoC的框架的常見特征。
在Spring框架中,接口ApplicationContext表示IoC容器。Spring容器負責實例化、配置和組裝稱為bean的對象,以及管理它們的生命周期。
Spring框架提供了*ApplicationContext
接口的幾個實現:
ClassPathXmlApplicationContext
和
FileSystemXmlApplicationContext
用于獨立應用程序,以及
WebApplicationContext*用于Web應用程序。
為了組裝bean,容器使用配置元數據,可以是XML配置或注釋形式。
以下是手動實例化容器的一種方法:
ini復制代碼ApplicationContext context
=newClassPathXmlApplicationContext("applicationContext.xml");Copy
在上面的示例中,我們可以使用元數據設置*item*屬性,然后容器將讀取此元數據并在運行時使用它來組裝bean。
在Spring中,可以通過構造函數、setter或字段來進行依賴注入。
基于構造函數的依賴注入
在基于構造函數的依賴注入的情況下,容器將調用具有表示我們要設置的依賴項的參數的構造函數。
Spring通過類型解決每個參數,然后按屬性名稱和索引進行消歧。讓我們看看使用注釋配置bean及其依賴項的配置:
scss復制代碼@Configuration
publicclassAppConfig {
@Bean
public Itemitem1() {
returnnewItemImpl1();
}
@Bean
public Storestore() {
returnnewStore(item1());
}
}Copy
*@Configuration*注釋表示該類是bean定義的源。我們也可以將其添加到多個配置類中。
我們在方法上使用@Bean注釋來定義bean。如果我們沒有指定自定義名稱,則bean名稱將默認為方法名稱。
對于默認的*singleton
范圍的bean,Spring首先檢查是否已存在緩存的bean實例,僅在不存在時創建新實例。如果我們使用
prototype*范圍,則容器為每個方法調用返回一個新的bean實例。
創建bean的另一種方式是通過XML配置:
ini復制代碼<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>Copy
基于setter的依賴注入
對于基于setter的DI,容器將在調用沒有參數的構造函數或沒有參數的靜態工廠方法來實例化bean之后調用我們類的setter方法。讓我們使用注釋創建此配置:
ini復制代碼@Bean
public Storestore() {
Store store =newStore();
store.setItem(item1());
return store;
}Copy
我們也可以使用XML進行相同的bean配置:
ini復制代碼<bean id="store" class="org.baeldung.store.Store">
<property name="item" ref="item1" />
</bean>Copy
我們可以將構造函數和setter類型的注入結合在同一個bean中。Spring文檔建議將基于構造函數的注入用于必需的依賴項,將基于setter的注入用于可選的依賴項。
基于字段的依賴注入
在基于字段的DI的情況下,我們可以通過帶有@Autowired注釋的注釋將依賴項注入其中:
JAVA復制代碼publicclassStore {
@Autowired
private Item item;
}Copy
在構造*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。
typescript復制代碼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:
kotlin復制代碼public class Store {
@Autowired
private Item item;
}
如果存在相同類型的多個bean,則可以使用@Qualifier注釋按名稱引用bean:
less復制代碼public class Store {
@Autowired
@Qualifier("item1")
private Item item;
}
現在,讓我們通過XML配置按類型自動裝配bean:
bash復制代碼<bean id="store" class="org.baeldung.store.Store" autowire="byType"> </bean>
接下來,讓我們通過XML按名稱將名為*item
的bean注入到
store
bean的
item*屬性中:
ini復制代碼<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*屬性:
csharp復制代碼<bean id="item1" class="org.baeldung.store.ItemImpl1" lazy-init="true" />
因此,只有在第一次請求它時,才會初始化*item1*bean,而不是在啟動時。這樣做的優點是初始化時間更快,但缺點是我們在bean被請求之后才會發現任何配置錯誤,這可能是應用程序已運行數小時甚至數天之后。
結論
在本文中,我們介紹了控制反轉和依賴注入的概念,并在Spring框架中進行了示例。