1.4,依存關系
典型的企業應用程式不坑你只包含單個物件或單個bean,即使是最簡單的應用程式,也有一些物件可以協同作業,以呈現最終用戶視為一致的應用程式,下一部分將說明如何從定義多個獨立的Bean定義到實作物件協作以實作目標的完全實作的應用程式,
1.4.1,依賴注入
依賴注入(Dependency injection,DI)是一個程序,物件通過建構式引數、工廠方法的引數或在構造或從工廠方法回傳后在物件實體上設定的屬性來定義它們的依賴項(即與它們一起作業的其他物件),
然后,容器在創建bean時注入這些依賴項,
這個程序基本上是bean本身的反向(因此得名,控制的反轉),通過使用類的直接構造或服務定位器模式,bean自己控制其依賴項的實體化或位置,
使用DI原則,代碼會更清晰,并且當向物件提供它們的依賴時,解耦會更有效,物件不查找其依賴項,也不知道依賴項的位置或類,因此,您的類變得更容易測驗,特別是當依賴關系在介面或抽象基類上時,它們允許在單元測驗中使用stub或mock實作,
依賴注入有兩種主要的變體:基于構造器的依賴注入和基于setter的依賴注入,
基于建構式的依賴注入
基于建構式的DI是通過容器呼叫具有多個引數(每個引數代表一個依賴項)的建構式來完成的,呼叫static帶有特定引數的工廠方法來構造Bean幾乎是等效的,并且本次討論將建構式和static工廠方法的引數視為類似,以下示例顯示了只能通過建構式注入進行依賴項注入的類:
public class SimpleMovieLister {
// SimpleMovieLister 依賴項 MovieFinder
private MovieFinder movieFinder;
// 建構式,以便Spring容器能夠注入MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注意,該類沒有什么特別的,它是一個POJO,不依賴于特定于容器的介面,基類或注釋,
建構式引數決議
建構式引數決議匹配通過使用引數的型別進行,如果Bean定義的建構式引數中不存在潛在的歧義,則在實體化Bean時,在Bean定義中定義建構式引數的順序就是將這些引數提供給適當的建構式的順序,考慮以下類別:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假設ThingTwo和ThingThree類不是通過繼承關聯的,則不存在潛在的歧義,因此,以下配置可以正常作業,并且您無需在
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
當參考另一個bean時,型別是已知的,并且可以發生匹配(與前面的示例一樣),當使用簡單的型別(例如)時
package examples;
public class ExampleBean {
private int years;
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
建構式引數型別匹配
在上述情況下,如果通過使用type屬性顯式指定建構式引數的型別,則容器可以使用簡單型別的型別匹配,如下例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value=https://www.cnblogs.com/dabaieyangzhijidi/p/"7500000"/>
建構式引數索引
您可以使用該index屬性來明確指定建構式引數的索引,如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value=https://www.cnblogs.com/dabaieyangzhijidi/p/"7500000"/>
除了解決多個簡單值的歧義性之外,指定索引還可以解決歧義,其中建構式具有兩個相同型別的引數,
索引從0開始,
建構式引數名稱
您還可以使用建構式引數名稱來消除歧義,如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value=https://www.cnblogs.com/dabaieyangzhijidi/p/"7500000"/>
請記住,要立即使用該功能,必須在啟用除錯標志的情況下編譯代碼,
以便Spring可以從建構式中查找引數名稱,
如果您不能或不想使用debug標志編譯代碼,則可以使用 @ConstructorProperties JDK注釋顯式命名建構式引數,
然后,該示例類必須如下所示:
package examples;
public class ExampleBean {
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter的依賴注入
基于設定器的DI是通過在呼叫無引數建構式或無引數static工廠方法實體化bean 之后,在bean上呼叫setter方法來完成的,
下面的示例顯示只能通過使用純setter注入來依賴注入的類,此類是常規的Java,它是一個POJO,不依賴于容器特定的介面,基類或注釋,
public class SimpleMovieLister {
private MovieFinder movieFinder;
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
并不是寫了上邊的類并把這個bean注冊進入Spring容器就能用setter注入
以下三種注冊bean的方式 只有 autowire = byType, autowire=byName 才會使用setter注入
autowire 默認屬性就是 no,詳細注入規則往后看
<bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="no"/>
<bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="byType"/>
<bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="byName"/>
ApplicationContext為它管理的bean支持基于構造器和基于setter的DI,在已經通過建構式方法注入了一些依賴項之后,它還支持基于setter的DI,您以BeanDefinition的形式配置依賴項,您將它與PropertyEditor實體一起使用,以將屬性從一種格式轉換為另一種格式,但是,大多數Spring用戶并不直接使用這些類(即通過編程方式),而是使用XML bean定義、帶注釋的組件(即用@Component、@Controller等注釋的類)或基于java的@Configuration類中的@Bean方法,然后在內部將這些源轉換為BeanDefinition的實體,并用于加載整個Spring IoC容器實體,
###### 基于建構式或基于setter的DI?
由于可以混合使用基于建構式的DI和基于setter的DI,因此將建構式用于強制性依賴項并將setter方法或配置方法用于可選依賴性是一個很好的經驗法則,
注意,可以 在setter方法上使用@Required批注,以使該屬性成為必需的依賴項,但是,最好使用帶有引數的程式驗證的建構式注入,
Spring團隊通常提倡建構式注入,因為它可以讓您將應用程式組件實作為不可變物件,并確保不存在必需的依賴項null,
此外,注入建構式的組件始終以完全初始化的狀態回傳到客戶端(呼叫)代碼,
附帶說明一下,大量的建構式自變數是一種不好的代碼味,這表明該類可能承擔了太多的職責,應將其重構以更好地解決關注點分離問題,
Setter注入主要應僅用于可以在類中分配合理的默認值的可選依賴項,
否則,必須在代碼使用依賴項的任何地方執行非空檢查,
setter注入的一個好處是,setter方法使該類的物件在以后可以重新配置或重新注入,
因此,通過JMX MBean進行管理是用于setter注入的引人注目的用例,
使用最適合特定班級的DI風格,
有時,在處理您沒有源代碼的第三方類時,將為您做出選擇,
例如,如果第三方類未公開任何setter方法,則建構式注入可能是DI的唯一可用形式,
依賴性決議程序
容器執行bean依賴項決議,如下所示:
- 用描述所有bean的配置元資料創建和初始化ApplicationContext,配置元資料可以由XML、Java代碼或注釋指定,,
- 對于每個bean,其依賴關系都以屬性,建構式引數或static-factory方法的引數的形式表示(如果使用它而不是普通的建構式),在實際創建Bean時,會將這些依賴項提供給Bean,
- 每個屬性或建構式引數都是要設定的值的實際定義,或者是對容器中另一個bean的參考,
- 作為值的每個屬性或建構式引數都將從其指定的格式轉換為該屬性或建構式引數的實際型別,默認情況下,Spring能夠以String型別提供值轉換成所有內置型別,比如int, long,String,boolean等等,
在創建容器時,Spring容器驗證每個bean的配置,
但是,bean屬性本身在實際創建bean之前是不會設定的,
在創建容器時,將創建單例范圍并設定為預實體化(預設值)的bean,
作用域在Bean作用域中定義,
否則,只在請求bean時創建它,創建一個bean可能會導致需要創建一系列的bean甚至是回圈依賴的bean,因為需要創建和分配bean的依賴項和bean的依賴項的依賴項(等等),
注意,這些依賴項之間的決議不匹配會在第一次創建受影響的bean時出現,
###### 回圈依賴
與典型的情況不同(沒有回圈依賴關系),bean A和bean B之間的回圈依賴關系迫使一個bean在完全初始化之前注入另一個bean(典型的先有雞還是先有蛋的場景),
如果您主要使用建構式注入,那么就會創建一個無法解決的回圈依賴場景,
例如:類A通過建構式注入需要一個類B的實體,類B通過建構式注入需要一個類A的實體,如果您為類A和類B配置了相互注入的bean,那么Spring IoC容器在運行時檢測到這個回圈參考,并拋出BeanCurrentlyInCreationException,
一個可能的解決方案是編輯一些類的源代碼,以便使用setter注入而不是構造器注入,或者,避免建構式注入,只使用setter注入,換句話說,盡管不建議這樣做,但您可以使用setter注入配置回圈依賴關系,
您通常可以相信Spring會做正確的事情,
它在容器加載時檢測bean不存在和回圈依賴關系的問題,
Spring當bean實際創建時會盡可能晚地設定屬性并決議依賴項,
這意味著,正確加載的Spring容器在創建物件或其依賴項時出現問題時,可以在請求物件時生成例外——例如,bean由于缺少或無效的屬性而拋出例外,
某些配置問題的潛在延遲可見性是ApplicationContext實作在默認情況下預實體化單例bean的原因,因為在實際需要之前創建這些bean需要花費一些前期時間和記憶體,所以默認是在創建ApplicationContext時發現配置問題,而不是稍后發現,
您仍然可以覆寫這個默認行為,以便單例bean可以惰性地初始化,而不是預先實體化,
如果不存在回圈依賴關系,那么當一個或多個協作bean被注入到依賴bean中時,每個協作bean在被注入到依賴bean之前都已被完全配置,
這意味著,如果bean A依賴bean B, Spring IoC容器會在呼叫bean A的setter方法之前完全配置bean B,
換句話說,bean實體化(如果它不是一個單例預先實體化),其設定依賴項,相關的生命周期方法(如InitializingBean init方法或配置回呼方法)呼叫,
依賴注入的例子
以下示例將基于XML的配置元資料用于基于setter的DI,Spring XML組態檔的一小部分指定了一些bean定義,如下所示:
此處examoleBean 的 autowire 默認屬性就是 no,手動指定了 屬性對應的bean
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value=https://www.cnblogs.com/dabaieyangzhijidi/p/"1"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
在前面的示例中,宣告了setter以與XML檔案中指定的屬性匹配,以下示例使用基于建構式的DI:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- constructor injection using the nested ref element -->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- constructor injection using the neater ref attribute -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value=https://www.cnblogs.com/dabaieyangzhijidi/p/"1"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
}
Bean定義中指定的建構式引數用作的建構式的引數ExampleBean,
現在考慮該示例的一個變體,在該變體中,不是使用建構式,而是告訴Spring呼叫static工廠方法以回傳物件的實體:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value=https://www.cnblogs.com/dabaieyangzhijidi/p/"1"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
private ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
static工廠方法的引數由
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/25911.html
標籤:Java
上一篇:第八題為什么選a呀d為什么不對
下一篇:搭建JavaWeb開發環境
