依賴注入
什麼是依賴注入?為什麼要有依賴注入?
典型的企業應用程式不包含單個對象(或Spring的說法中的bean)。即使最簡單的應用程式也有幾個對象一起工作,将最終的結果展示出來的程式作為一個整體的應用。使用依賴注入将一個個分散開來、沒有聯系的Bean注入到一起,一起完成工作。
一、依賴注入的解釋
依賴注入(DI)是一個過程,通過這種過程,對象可以通過構造函數參數,工廠方法參數或者在構造或傳回對象執行個體後設定的屬性來定義它們的依賴關系,
-
控制反轉
容器在建立bean時會注入這些依賴關系。
這個過程從根本上來說是反的,是以名為控制反轉(IoC),它本身通過使用類的直接構造或服務定位器模式來控制它自己的依賴關系的執行個體化或位置。
-
單純的普通代碼與依賴注入(DI)的差別
代碼與DI原理相比更加清晰,并且在對象提供依賴關系時解耦更有效。該對象不查找其依賴項,并且不知道依賴項的位置或類。是以,您的類變得更容易測試,特别是當依賴關系位于接口或抽象基類上時,它們允許在單元測試中使用存根或模拟實作。
二、兩種主要的DI方式
基于構造器的依賴注入和基于Setter的依賴注入。
2.1 基于構造器的依賴注入
基于構造器的 DI通過容器調用具有多個參數的構造函數完成,每個參數表示一個依賴項。調用static具有特定參數的工廠方法來構造bean幾乎是等價的,本讨論static類似地将參數視為構造函數和工廠方法。以下示例顯示了隻能通過構造函數注入進行依賴注入的類。請注意,這個類沒有什麼特别之處,它是一個POJO,它不依賴于容器特定的接口,基類或注釋。
2.1.1
- 首先建立一個需要注入的類
package org.springframework.test;
/**
* @author:wangdong
* @description:建立一個MovieFinder的bean
*/
public class MovieFinder {
}
- 建立一個被注入的類
package org.springframework.test;
/**
* @author:wangdong
* @description:基于構造器的依賴注入
*/
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
//現在想将這個MovieFinderBean注入到SimpleMovieLister裡面去
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
//建立一個SimpleMovieLister的構造器,MovieFinder作為構造參數傳入這個構造器
public SimpleMovieLister(MovieFinder movieFinder){
this.movieFinder = movieFinder;
}
}
2.1.2 寫一個注入類的例子
- 定義兩個類
package org.springframework.test;
/**
* @author:wangdong
* @description:Bar類
*/
public class Bar {
}
package org.springframework.test;
/**
* @author:wangdong
* @description:
*/
public class Baz {
}
- 定義一個Foo類,定義一個構造
package org.springframework.test;
/**
* @author:wangdong
* @description:Foo類
*/
public class Foo {
public Foo(Bar bar,Baz baz){
}
}
在上述中,使用參數的類型産生構造函數參數解析比對。如果bean定義的構造函數參數中沒有可能存在的歧義,那麼在bean定義中定義構造函數參數的順序是當執行個體化bean時将這些參數提供給相應構造函數的順序。
沒有潛在的歧義存在,假設Bar和Baz類不通過繼承相關。是以,以下配置可以正常工作,并且不需要在
<constructor-arg/>
元素中明确指定構造函數參數索引和/或類型。
- foo.xml執行個體化容器的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="org.springframework.test.Foo">
<!-- 因為Bar,Baz都是類,且沒有沖突和歧義項,是以無需制定參數的類型-->
<constructor-arg index="0" ref="bar"/>
<constructor-arg index="1" ref="baz"/>
</bean>
<bean id="bar" class="org.springframework.test.Bar"></bean>
<bean id="baz" class="org.springframework.test.Baz"></bean>
</beans>
2.1.3寫一個構造器注入參數值的例子
- ExampleBean類
package org.springframework.test;
/**
* @author:wangdong
* @description:引入參數不為特定類,需要制定參數的類型
*/
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
//這邊在執行個體化Bean的時候需要在.xml檔案中指定類型
public ExampleBean(int years, String ultimateAnswer){
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
- exampleBean.xml配置依賴注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 構造函數參數類型比對 使用type關鍵字-->
<!-- 如果使用屬性明确指定構造函數參數的類型,容器可以使用簡單類型的類型比對type-->
<!--<bean id="exampleBean" class="org.springframework.test.ExampleBean"">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>-->
<!-- 構造函數參數索引-->
<!-- 使用該index屬性來明确指定構造函數參數的索引。-->
<!-- 0表示第一個參數,1表示第二個參數 -->
<!-- 除了解決多個簡單值的歧義之外,指定索引還解決了構造函數具有相同類型的兩個參數的不明确性。請注意,該 索引是基于0的。-->
<!--<bean id="exampleBean" class="org.springframework.test.ExampleBean"">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>-->
<!--構造函數參數名稱,您也可以使用構造函數參數名稱進行值消歧:-->
<bean id="exampleBean" class="org.springframework.test.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
</beans>
2.1.4 基于Setter的依賴注入
基于Setter的 DI通過調用無參數構造函數或無參數static工廠方法來執行個體化bean之後,調用bean上的容器調用setter方法來完成。
- 以下示例顯示了一個隻能使用純setter注入進行依賴注入的類。這個類是傳統的Java。這是一個POJO,它不依賴于容器特定的接口,基類或注釋。
package org.springframework.test;
/**
* @author:wangdong
* @description:基于構造器的依賴注入
*/
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
//現在想将這個MovieFinderBean注入到SimpleMovieLister裡面去
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder){
this.movieFinder = movieFinder;
}
}
三、ApplicationContext對兩種依賴注入的支援
該ApplicationContext容器支援基于構造器的依賴注入和基于Setter方法的依賴注入兩種方式。
它也支援一些依賴通過Setter的方式注入,一些已經通過構造器方式注入。
您以a的形式配置依賴關系BeanDefinition,您可以結合PropertyEditor執行個體将屬性從一種格式轉換為另一種格式。
大多數Spring使用者不直接與這些類(即,程式設計),而是用XML bean 定義,注釋元件(即與注釋類@Component, @Controller等等),或@Bean在基于Java的方法@Configuration類。然後将這些源内部轉換為執行個體BeanDefinition并用于加載整個Spring IoC容器執行個體。
3.1 選擇構造器注入還是基于Setter的注入方式
既然你可以混合使用基于構造函數和基于setter的DI,對于可選的依賴項,使用強制依賴和構造方法的構造函數或配置方法是一個很好的經驗法則。請注意, 在setter方法上使用@Required注釋可用于使屬性成為必需的依賴項。
Spring團隊通常主張構造器注入,因為它使得人們可以将應用程式元件實作為不可變對象,并確定所需的依賴關系不是null。此外,構造器注入的元件總是以完全初始化的狀态傳回給用戶端(調用)代碼。作為一個側面說明,大量的構造函數參數是一種糟糕的代碼異味,這意味着該類可能有太多的責任,應該重構以更好地解決問題的分離問題。
Setter注入主要隻應用于可選的依賴關系,這些依賴關系可以在類中配置設定合理的預設值。否則,必須在代碼使用依賴關系的任何地方執行非空檢查。setter注入的一個好處是setter方法使得該類的對象可以重新配置或稍後重新注入。通過JMX MBeans進行管理是以是一個引人注目的setter注入用例。
使用對特定類最有意義的DI風格。有時候,在處理沒有源代碼的第三方類庫時,可以靈活選擇。例如,如果第三方類不公開任何setter方法,則構造函數注入可能是DI的唯一可用形式。
四、依賴性解決的過程
4.1 該容器執行bean依賴性解析如下
- 使用ApplicationContext描述所有bean的配置中繼資料建立并初始化該元素。配置中繼資料可以通過XML,Java代碼或注釋來指定。
- 對于每個bean,如果使用該屬性而不是普通構造函數,則它的依賴關系以屬性,構造函數參數或靜态工廠方法的參數的形式表示。當bean被實際建立時,這些依賴被提供給bean 。
- 每個屬性或構造函數參數都是要設定的值的實際定義,或者是對容器中另一個bean的引用。
- 作為值的每個屬性或構造函數參數都從其指定的格式轉換為該屬性或構造函數參數的實際類型。預設情況下,Spring能夠轉換成字元串格式提供給所有的内置類型,比如數值int, long,String,boolean,等。
- Spring容器在容器建立時驗證每個bean的配置。但是,在實際建立 bean之前,bean屬性本身不會被設定。Beans是單身作用域并且被設定為預先執行個體化的(預設的)是在建立容器時建立的。範圍在Bean範圍中定義。否則,隻有在請求時才建立bean。建立一個bean可能會導緻建立一個bean圖,因為bean的依賴關系及其依賴關系的依賴關系(等等)被建立和配置設定。請注意,這些依賴項之間的解決方案不比對可能會出現晚,即首次建立受影響的bean。
4.2 循環依賴
如果您主要使用構造函數注入,則可能會建立一個無法解析的循環依賴方案。
-
例如:類A需要通過構造函數注入的類B的執行個體,而類B需要通過構造函數注入的類A的執行個體。如果将類A和B的Bean配置為互相注入,則Spring IoC容器會在運作時檢測到此循環引用,并引發一個 BeanCurrentlyInCreationException。
一種可能的解決方案是編輯某些類的源代碼,以便由setter而不是構造器進行配置。或者,避免構造函數注入并僅使用setter注入。換句話說,雖然不推薦,但您可以使用setter注入來配置循環依賴關系。
與典型情況(沒有循環依賴)不同,bean A和bean B之間的循環依賴關系迫使其中一個bean在被完全初始化之前注入另一個bean(經典的雞/雞蛋場景)。
- 通常你可以相信Spring架構在做正确的事情。它在容器加載時檢測配置問題,例如引用不存在的bean和循環依賴關系。當bean實際建立時,Spring會盡可能晚地設定屬性并解決依賴關系。這意味着,如果在建立該對象或其某個依賴關系時遇到問題,那麼請求對象時,正确加載的Spring容器可能會稍後生成異常。例如,由于缺少或無效的屬性,bean抛出異常。某些配置問題的可能延遲可見性是原因ApplicationContext實作預設預先執行個體化單例bean。在實際需要這些bean之前,為了建立這些bean需要一定的時間和記憶體,您ApplicationContext會在建立時發現配置問題,而不是稍後。您仍然可以重寫此預設行為,以便單例bean将會進行延遲初始化,而不是預先執行個體化。
- 如果不存在循環依賴關系,則當一個或多個協作bean被注入到一個依賴bean中時,每個協作bean都被注入到依賴bean 之前完全配置。這意味着如果bean A對bean B有依賴性,Spring IoC容器在調用bean A上的setter方法之前完全配置bean B.換句話說,bean被執行個體化(如果不是預先執行個體化的單例),它的将設定依賴關系,并調用相關的生命周期方法
五、依賴注入的例子
5.1下面這個例子是基于Setter依賴注入的,它使用了XML檔案最為中繼資料配置檔案。
- ExampleBean
package org.springframework.test.examples;
/**
* @author:wangdong
* @description:基于Setter的依賴注入
*/
public class ExampleBean {
private AnotherBean anotherBean;
private YetAnotherBean yetAnotherBean;
private int integerProperty;
//基于Setter的依賴注入
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
public void setYetAnotherBean(YetAnotherBean yetAnotherBean) {
this.yetAnotherBean = yetAnotherBean;
}
public void setIntegerProperty(int integerProperty) {
this.integerProperty = integerProperty;
}
}
- exampleBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 需要被注入的Bean -->
<bean id="exampleBean" class="org.springframework.test.examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<!-- ref引用下面的兩個Bean-->
<property name="anotherBean">
<ref bean="anotherBean"></ref>
</property>
<property name="yetAnotherBean">
<ref bean="yetAnotherBean"></ref>
</property>
<!-- 引入非Bean的參數,需要給定一個值,例如1-->
<property name="integerProperty" value="1"></property>
</bean>
<!-- 下面的兩個Bean是要注入到ExampleBean中的-->
<!-- 引入的非Bean的參數就不用在這邊寫出來-->
<bean id="anotherBean" class="org.springframework.test.examples.AnotherBean"></bean>
<bean id="yetAnotherBean" class="org.springframework.test.examples.YetAnotherBean"></bean>
</beans>
5.2 下面的例子是基于構造器注入,.xml檔案配置中繼資料資料
package org.springframework.test.examples.constructor;
/**
* @author:wangdong
* @description:基于構造器的依賴注入
*/
public class ExampleBean {
private AnotherBean anotherBean;
private YetAnotherBean yetAnotherBean;
private int integerProperty;
//基于構造器的依賴注入
public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int integerProperty){
this.anotherBean = anotherBean;
this.yetAnotherBean = yetAnotherBean;
this.integerProperty = integerProperty;
}
}
- ExampleBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="exampleBean" class="org.springframework.test.examples.constructor.ExampleBean">
<!--引入Bean -->
<constructor-arg>
<ref bean="anotherBean"></ref>
</constructor-arg>
<constructor-arg>
<ref bean="yetAnotherBean"></ref>
</constructor-arg>
<!--引入參數-->
<constructor-arg type="int" value="1"></constructor-arg>
</bean>
<!-- 引入Bean-->
<bean id="anotherBean" class="org.springframework.test.examples.constructor.AnotherBean"></bean>
<bean id="yetAnotherBean" class="org.springframework.test.examples.constructor.YetAnotherBean"></bean>
</beans>
六、下面來考慮用一個Static靜态工廠
靜态工廠的工廠方法來傳回一個對象的例子(嚴格意義上不屬于依賴注入的兩種方式)。
- StaticExampleBean
package org.springframework.test.examples.staticexample;
/**
* @author:wangdong
* @description:靜态工廠的工廠方法傳回一個執行個體
*/
public class StaticExampleBean {
//一個私有的有參構造
//下面會引用它
private StaticExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i){
}
// a static factory method; the arguments to this method can be
// considered the dependencies of the bean that is returned,
// regardless of how those arguments are actually used.
//一個靜态工廠方法,他與依賴注入的差别是他可以不傳回一個StaticExampleBean的Bean
//StaticExampleBean在這裡是指傳回的資料的類型,現在是StaticExampleBean
//參數anotherBean、yetAnotherBean和i,都不要直接在類中聲明出來
public static StaticExampleBean createInstance(AnotherBean anotherBean, YetAnotherBean yetAnotherBean,int i){
StaticExampleBean staticExampleBean = new StaticExampleBean(anotherBean,yetAnotherBean,i);
return staticExampleBean;
}
}
- StaticExampleBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="staticExampleBean" class="org.springframework.test.examples.staticexample.StaticExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherBean"></constructor-arg>
<constructor-arg ref="yetAnotherBean"></constructor-arg>
<constructor-arg value="1"></constructor-arg>
</bean>
<bean id="anotherBean" class="org.springframework.test.examples.staticexample.AnotherBean"></bean>
<bean id="yetAnotherBean" class="org.springframework.test.examples.staticexample.YetAnotherBean"></bean>
</beans>
好啦,結束啦,感謝大家的支援。