一個典型的企業應用不是由一個簡單的對象(在 Spring
中叫bean)組成。即使是最簡單的應用程式,也有一些對象協同工作,以呈現最終使用者視為一緻的應用程式。(備注:相當于所有的bean一起協同工作對于使用者是無感覺的)。下一部分将說明如何從定義多個獨立的Bean對象協作去實作應用程式的目标。
1.4.1 依賴注入
依賴注入是從工廠方法構造或傳回的執行個體并通過設定對象執行個體的構造參數、工廠方法參數或者屬性去定義它的依賴關系(與它一起工作的對象)的過程。當建立
bean
的時候容器注入這些依賴。從根本上講,此過程是通過使用類的直接構造或服務定位器模式來控制 bean
自身依賴關系的執行個體化或位置的 bean
本身的逆過程(是以稱為控制反轉)。
DI(依賴注入)使代碼更簡潔和解偶,當為這些對象提供依賴時候是更高效的。(通過依賴注入來注入對象更高效)。對象不用去主動查找它的依賴項并且不用知道依賴類的位置。這樣的結果是我們的類變成了更容易被測試,特别地,當這些依賴在接口或者抽象類上時,在單元測試中去使用
stub
或者 mock
方式去實作這些接口和抽象類。
DI(依賴注入)存在兩個重要的變體:
基于構造函數的依賴注入 和 基于Setter的依賴注入- 基于構造函數依賴注入
基于構造函數的DI(依賴注入)是通過容器調用構造函數完成的,每一個構造函數參數代表一個依賴。調用帶有特定參數的靜态工廠方法來構造Bean幾乎是等效的,并且本次讨論将構函數和靜态工廠方法的參數視為類似的。下面的例子展示了隻能通過構造函數進行對象注入:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
//構造函數注入MovieFinder對象
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
注意:這個類沒有任何特别的。它是一個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時,類型是已知的,可以進行比對。當一個簡單類型被使用,例如true,Spring不能确定這個值的類型,是以在沒有類型的幫助下是不能被比對的。考慮下面的類:
package examples;
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;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
- 構造函數參數類型比對
在前面的場景中,如果我們通過使用
type
屬性明确指定了構造函數參數類型,容器會使用簡單類型進行比對。像下面的例子:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
- 構造函數參數索引
我們可以明确指定構造函數參數的索引通過
index
屬性,像下面例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!--指定第一個參數-->
<constructor-arg index="0" value="7500000"/>
<!--指定第二個參數-->
<constructor-arg index="1" value="42"/>
</bean>
除了解決多個簡單值的歧義性之外,指定索引還可以解決歧義,其中構造函數具有兩個相同類型的參數。
index索引從0開始
- 構造函數參數名稱
我們也可以使用構造函數參數名稱來消除歧義,例如下面例子:
<bean id="exampleBean" class="examples.ExampleBean">
<!--指定構造函數參數-->
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
請記住,要使此工作開箱即用,必須在啟用調試标志的情況下編譯代碼,以便Spring可以從構造函數中查找參數名稱。如果不能或不想在debug标記下編譯代碼,可以使用JDK注解
@ConstructorProperties去明确構造函數參數的名稱。參考下面例子:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
- 基于Setter依賴注入
基于
Setter
的 DI(依賴注入)是在bean調用無參構造函數或無參
static
工廠函數去執行個體化
bean
後被容器調用函數去完成的。
下面的例子展示了一個隻能通過使用
Setter
注入的類。這個類是正常的Java。它是一個POJO并且沒有依賴容器特定接口、基礎類或者注解。
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// 通過Setter進行注入
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext
支援基于構造函數和
Setter
的依賴注入的
bean
管理。也支援在通過構造函數注入後再通過基于
Setter
方法注入。可以使用
BeanDefinition
的形式配置依賴項,将其與
PropertyEditor
執行個體結合使用以從一種格式轉換為另一種格式。然後,大多數
Spring
使用者不會直接使用這些類,而是使用XML的bean定義、注解這些元件(類似
@Component
,
@Controller
等等),或者基于Java被标注
@Configuration
類的方法使用
@Bean
标注。這些配置資料源内部地轉換為
BeanDefinition
執行個體并且被使用于加載整個
Spring IoC
容器執行個體。
基于構造函數和
Setter
函數注入選擇
由于可以混合基于構造函數和
函數的依賴注入,将構造函數用于強制性依賴項,将
Setter
方法或配置方法用于可選性依賴項是一個很好的經驗法則。需要注意在
Setter
方法上使用 @Required 表明這個屬性需要一個依賴;然而,構造函數注入可以通過程式設計方式校驗參數是可取的。
Setter
團隊一般推薦使用建構函數注入,因為可以允許我們去實作不可變的對象元件并且確定需要的依賴不為
Spring
。此外,構造函數注入元件總是被傳回一個完整初始化的狀态。構造函數大量的參數是一個壞代碼味道,暗示着這個有太多的責任并且應該去重構以更好的分離關注點問題。
null
注入應該主要使用在可選依賴因為可以在在類中設定一個預設值。否則,必須在代碼使用依賴項的任何地方執行非空檢查。
Setter
注入的一種好處是可以在後面對
Setter
Setter
方法進行重新配置或重新注入。
使用對特定類最有意義的DI樣式,有時候,當處理第三方類庫沒有源碼的時候,這個選擇是非常适合的。例如:如果第三方類庫沒有暴露任何的
方法,構造函數注入可能是依賴注入的唯一有效方式。
Setter
- 依賴解析處理
容器執行bean依賴解析過程:
-
通過對所有bean的配置中繼資料描述進行建立和初始化。配置中繼資料通過ApplicationContext
、XML
或者注解描述。JavaConfig
- 對于每個
,它的依賴形式通過屬性、構造函數參數或者bean
(如果使用正常的構造函數替換)方法參數表達。當這個bean被建立的時候,這些依賴被提供給static-factory
。(備注:被依賴bean先建立)bean
- 每一個屬性或者構造函數參數的都要設定一個實際的定義,或引用容器其他
。bean
- 每一個屬性或構造函數參數的值從指定的格式轉換為屬性或構造函數參數的真實類型。預設情況下,Spring提供一個字元串格式轉換為所有内建類型的值,例如:int、long、String、boolan等等。
Spring容器驗證每一個建立bean的配置。然而,bean屬性本身沒有被設定,直到bean被真正建立。在建立容器時,将建立單例作用域的bean并将其設定為預執行個體化(預設值)。Scope被定義在
Bean Scopes。除此之外,其他的bean建立僅僅在請求容器的時候。bean的建立潛在的導緻一些bean的圖被建立(備注:意思是bean所依賴的bean被建立,類似于bean的依賴拓撲圖),類似bean的依賴和它的依賴的依賴bean建立和被指派。注意:這些依賴項之間的解析不比對可能會在第一次建立受影響的bean時出現。
循環依賴
如果主要使用構造函數注入,則可能會建立無法解決的循環依賴場景。
例如:類
通過構造函數需要依賴注入類
A
并且類
B
通過構造函數依賴注入
B
。如果配置類
A
和類
A
互相依賴注入,Spring IoC容器在運作時檢測到循環依賴會抛出一個
B
BeanCurrentlyInCreationException
異常。
一種解決方法是編輯類的源碼通過
而不是構造函數注入。或者避免使用構造函數注入而是僅僅使用
Setter
方法注入。換句話說,雖然它是不推薦使用的,我們可以通過
Setter
注入配置循環依賴對象。與典型情況(沒有循環依賴關系)不同,Bean
Setter
和Bean
A
之間的循環依賴關系迫使其中一個Bean在完全初始化之前被注入另一個Bean(經典的“雞與蛋”場景)
B
通常,你可以相信
Spring
做的事情是正确的。容器會在加載時候檢測配置問題,例如:引用不存在的
bean
、循環依賴。當這個
bean
被真正建立的時候,
Spring
設定屬性并且盡可能晚的解析依賴。這意味着如果建立該對象或其依賴項時遇到問題,則已正确加載的
Spring
容器可能在你請求對象時生成異常-例如,這個
bean
抛出一個錯誤或無效的屬性異常結果。這可能會延遲某些配置問題的可見性,這就是為什麼預設情況下ApplicationContext實作會預先執行個體化單例bean的原因。在實際需要使用這些bean之前要花一些前期時間和記憶體,你會在建立ApplicationContext時發現配置問題,而不是以後(使用bean的時候)。你可以覆寫這個預設行為,這樣單例bean就可以惰性地初始化,而不是預先執行個體化。
如果不存在循環依賴關系,則在将一個或多個協作bean注入到依賴
bean
中時,每個協作
bean
在注入到依賴
bean
中之前都已完全配置。也就是,如果bean
A
有依賴bean
B
,
Spring IoC
容器在調用bean
A
的
Setter
方法之前完整的配置bean
B
。換句話說,這個bean被執行個體化,他的依賴被設定并且關聯的生命周期函數(例如:
init方法 InitializingBean 回調函數)已經被調用。
- 依賴注入例子
下面的例子基于XML配置中繼資料去配置基于
Setter
的依賴注入。Spring XML配置檔案指定一些bean的定義:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 屬性注入:依賴一個bean -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- 屬性注入:依賴一個bean-->
<property name="beanTwo" ref="yetAnotherBean"/>
<!-- 屬性值填充-->
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面定義
ExampleBean
的類
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
// 指定屬性需要注入bean類型
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
// 指定屬性需要注入bean類型
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
上面的例子,在XML檔案中通過
Setter
聲明屬性比對類型。下面例子使用構造函數注入:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 構造函數注入bean anotherExampleBean-->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- 構造函數注入bean yetAnotherBean-->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
下面例子對于
ExampleBean
的類定義
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
構造函數的參數(意思是xml中指定的構造函數引用作為
ExampleBean
構造函數的參數)。
現在考慮這個例子的變體,使用構造函數替換,
Spring
調用
static
工廠方法去傳回對象的執行個體:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
ExampleBean
對應的類定義:
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
//靜态工廠方法
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
靜态工廠方法的參數由元素提供,與實際使用構造函數時完全相同。通過工廠方法傳回的類的類型不必與包含靜态工廠方法的類的類型相同(盡管在此例中為相同)。執行個體(非靜态)工廠方法可以以基本上相同的方式使用(除了使用factory-bean屬性代替class屬性之外),是以不在這裡詳細讨論。
參考代碼: com.liyong.ioccontainer.starter.XmlDependecyInjectContainer
1.4.2 詳細介紹依賴項和配置
在前面的章節提到,我們可以定義bean的屬性和構造函數參數去引用其他被管理的bean(協同者)或者作為一個内聯值定義。Spring的XML中繼資料配置支援子元素類型和這個為了這個設計目的。
- 直接值(原生類型、字元串等)
元素的value屬性将屬性或構造函數參數指定為人可讀的字元串表示形式。Spring的
conversion service被使用從字元串轉換屬性或參數的實際類型。下面的例子展示各種值的設定:
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
下面的例子使用
p
命名空間使
xml
配置方式更簡潔:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用p命名空間 注意: xmlns:p="http://www.springframework.org/schema/p -->
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
前面的XML配置非常的簡潔,但是,拼寫錯誤在運作時被發現而不是在設計時,除非我們使用IDE(例如:
Intellij IDEA
Spring Tools
)支援自動屬性完成當我們建立
bean
定義的時候。IDE助手是非常推薦的。
我們可以配置
java.util.Properties
執行個體,例如:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- java.util.Properties類型配置 -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring容器通過使用
JavaBeans
的PropertyEditor機制轉換元素值為
java.util.Properties
。這是一個不錯的捷徑,并且是Spring更喜歡使用嵌套的元素而不是value屬性樣式。
-
元素idref
idref
元素是一個簡單的容錯方式,将容器中另外bean的id傳遞(字元串值-不是引用)到 或元素。下面的例子展示怎樣去使用:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
前面bean定義片段和下面的片段相同:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
com.liyong.ioccontainer.starter.XmlIocContainer
第一種形式優于第二種形式,因為使用
idref
标記可使容器在部署時驗證所引用的名Bean實際上是否存在。在第二個變體中,不對傳遞給用戶端bean的targetName屬性的值執行驗證。拼寫錯誤僅在實際執行個體化用戶端bean時才發現(最有可能導緻緻命的結果)。如果這個用戶端bean是原型bean,這個拼寫和結果異常可能在這個容器部署後很久才被發現。
在标簽元素上的的
idref
屬性在
local
後不在支援。因為它沒有提供正常的
spring-beans.xsd 4.0
引用值。當更新到
bean
更改
spring-beans.xsd 4.0
為
idref local
idref bean
idref
标簽元素帶來的價值的地方是在
ProxyFactoryBean
bean
定義中配置
AOP攔截器。指定攔截器名稱時使用元素可防止你拼寫錯誤的攔截器ID。
- 引用其他的bean
ref
元素是
<constructor-arg/>
或中定義的最後一個元素。在這裡,我們通過設定一個
bean
的指定屬性的值引用被容器管理的其他
bean
。引用的
bean
是要設定其屬性的
bean
的依賴關系,并且在設定屬性之前根據需要對其進行初始化。(如果這個協同者是一個單例
bean
,它可能已經被容器初始化)所有的引用最終引用其他對象。
bean
的範圍和校驗依賴你是否有指定其他對象通過
bean
parent
屬性指定的
id
name
指定目标bean通過标簽的bean屬性是最常見的形式并且允許在同一個容器或父容器引用任何的被建立的
bean
,而不管是否在同一個
XML
配置檔案。bean屬性的值可以與目标bean的id屬性相同,也可以與目标bean的name屬性中的值之一相同。下面例子展示怎樣使用
ref
元素。
<bean id="userService" class="com.liyong.ioccontainer.service.UserService">
<!--屬性注入 儲存一種方式就可以-->
<property name="bookService">
<ref bean="bookService"/>
</property>
</bean>
通過
parent
屬性指定目标
bean
的引用,這個
bean
在目前容器的父容器中。
parent
屬性的值可以與目标
Bean
id
屬性或目标
Bean
name
屬性中的值之一相同(
id
name
指定引用)。這個目标
bean
必須在父容器中。當你有一個分層的容器并且你想去包裝一個在父容器中存在的
bean
為代理對象同時有一個相同的名字作為這個父
bean
,你應該主要的使用這個
bean
應用的變體。下面的兩個例子展示類怎樣使用
parent
屬性。
<!--在父容器上下文-->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- 在子容器上下文 -->
<!-- 産生一個代理bean,bean name is the same as the parent bean -->
<bean id="accountService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/>
<!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
- 内部bean
或元素在内定義内部bean,像下面例子展示:
<!-- 外部bean定義 -->
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<!-- 内部bean定義 -->
<bean class="com.example.Person">
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
一個内部
bean
定義不需要定義一個
id
或名稱。如果指定名稱,這個容器不會使用這個值作為辨別(不會使用定義的作為
id
或
name
辨別)。容器在建立内部bean或忽略
Scope
(作用域),因為内部
bean
總是匿名的并且總是依賴外部
bean
的建立。不可能獨立通路内部
bean
或将它們注入到協作
bean
中而是封裝在
bean
中。一個極端的情況,可能接受定制的Scope的銷毀回調-例如:一個請求域内部
bean
包含在一個單例
bean
中。内部bean執行個體的建立與其包含的bean綁定在一起,但是銷毀回調使它可以參與請求範圍的生命周期。這不是常見的情況。内部bean通常隻共享其包含bean的作用域。
com.liyong.ioccontainer.starter.XmlOutterInnerBeanIocContainer
- 集合
、、和元素分别設定Java集合類型List、Set、Map和Properties的屬性和參數。下面例子展示怎樣使用:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- adminEmails屬性為Properties類型。 results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
<prop key="development">[email protected]</prop>
</props>
</property>
<!--someList屬性為java.util.List類型。 results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!--someMap屬性類型為:java.util.Map。 results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
</map>
</property>
<!--someSet屬性類型為:java.util.Set。 results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
Map key的值、值、或者set的值,可以是任何下面元素:
bean | ref | idref | list | set | map | props | value | null
com.liyong.ioccontainer.starter.XmlCollectionsIocContainer
- 集合合并
Spring容器也支援對集合合并。應用程式開發人員可以定義、、或元素有一個子元素集合、、或繼承和覆寫父集合元素。是以,子集合的值是父集合和子集合合并元素後的結果,也就是子集合元素會覆寫父集合的元素值。
在合并章節讨論父-子bean的機制。不熟悉父bean和子bean定義的讀者可能希望先閱讀
相關部分,然後再繼續。
下面的例子展示集合的合并:
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">[email protected]</prop>
<prop key="support">[email protected]</prop>
</props>
</property>
</bean>
<beans>
注意:在
child
bean
定義的屬性
adminEmails
的元素的屬性是
merge=true
。當這個
child
bean被容器解析和初始化的時候,這個結果執行個體有
adminEmails
Properties
集合,這個集合包含了子集合和父集合的adminEmails合并集。結果清單:
[email protected]
[email protected]
[email protected]
為了支援在父
Properties
值被覆寫,子
Properties
集合的值從父中繼承所有的值和子
Properties
的值(備注:意思是子Properties會覆寫父
Properties
中重複的值)。
這個合并行為适用類似、、集合類型 。在元素的特定情況下,将維護與List集合類型關聯的語義(即,值有序集合的概念)。父元素的值優先與所有的子元素值。在
Map
Set
Properties
集合類型中不存在順序。是以,對于容器内部使用的關聯
Map
Set
Properties
實作類型下的集合類型,沒有有效的排序語義。
- 集合合并限制
我們不能合并不同集合類型(例如:Map和List)。如果嘗試去合并将會抛出一個Exception異常。這個merge屬性必須被指定在子類中。在父集合定義中指定merge屬性是多餘的并且不會達到預期結果。
- 強類型集合
在Java 5中泛型被引入,我們可以使用強類型集合。是以,僅僅包含
String
元素的集合聲明成為可能。如果我們使用Spring去依賴注入一個強類型的Collection到一個bean中,可以利用Spring的類型轉換在添加到Collection集合前對集合執行個體元素轉換為适合的類型。下面的java代碼和bean定義展示了怎樣去使用:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
當準備注入
something
bean的
accounts
屬性時,可以通過反射獲得有關強類型
Map <String,Float>
的元素類型的泛型資訊。是以,
Spring
的類型轉換基礎設施識别各種元素
Float
類型的值并且這些字元串值能夠被轉換為真實的
Float
類型。
- Null和空字元串值
Spring将屬性等的空參數視為空字元串。下面基于XML的配置中繼資料片段設定了email屬性為空字元串
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
和下面java代碼相等:
exampleBean.setEmail("");
元素被處理為null值,下面展示例子:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
exampleBean.setEmail(null);
-
快捷方式p-namespace
p-namespace
讓你使用
bean
元素的屬性(嵌入元素替換)去描述你的屬性值協同者bean,或者兩種都使用。
Spring支援可擴充的
namespace配置格式,基于XML Schema定義。本章讨論的bean配置格式在XML Schema文檔中定義。然而,
p-namespace
在XSD檔案中沒有被定義僅僅在
Spring Core
中存在。
下面例子展示兩個XML片段其解析結果是一緻的.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="[email protected]"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
</beans>
該示例顯示了
p-namespace
中的一個屬性,該屬性在bean定義中稱為
email
。這告訴
Spring
包含一個屬性的聲明。前面提到,
p-namespace
沒有
schema
定義,是以你可以設定屬性名稱為
property
(類字段)名稱。
下一個示例包括另外兩個bean定義,它們都有對另一個bean的引用:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern"
class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
</beans>
這個例子包含了不僅屬性值使用
p-namespace
而且還使用指定格式去聲明屬性引用。第一個定義使用去建立一個從bean
john
到bean
jane
的引用,第二個
bean
定義使用
p:spouse-ref="jane"
作為一個屬性去做相同的事情。在這個例子中,
spouse
是屬性名稱,
ref
表示不是一個直接值而是一個引用值。
不像标準的XML格式靈活。例如,聲明屬性引用的格式與以Ref結尾的屬性沖突,而标準XML格式則不會。我們推薦你選擇你的方式小心地并和你的團隊交流去避免在用一時間同時使用XML三種方式。
p-namespace
com.liyong.ioccontainer.starter.XmlPNameSpaceIocContainer
-
c-namespace
類似
p-namespace的
快捷方式,在
Spring3.1
引入,
c-namespace
允許配置構造函數參數内聯屬性而不是嵌入
constructor-arg
c:
命名空間去做相同的基于
構造函數依賴注入事情:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- 傳統聲明可選參數 -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="[email protected]"/>
</bean>
<!-- c-namespace聲明參數名稱 -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="[email protected]"/>
</beans>
c:
命名空使用類似于
p:
相同的限制(
bean
引用為跟随
-ref
)去通過它們的名字設定構造參數。類似地,即使它沒有在
XSD schema
中定義,也需要在XML檔案中去聲明,(存在
Spring Core
中)。
一個少見的場景,構造函數參數名字不能使用(通常如果編譯位元組碼時沒有調試資訊)可以使用回退參數索引,如下:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="[email protected]"/>
由于XML文法,要求這個索引符号 _
必須存在,因為XML屬性名稱不能以數字開頭(即使IDE工具允許也是不行的)。對應索引的符号在元素也是有效的但是不常用,因為這個聲明順序通常已經足夠。
在實踐中,構造函數解析機制在比對參數是非常高效的,是以,除非你真的需要,我們推薦整個配置都使用名字元号。
com.liyong.ioccontainer.starter.XmlCNameSpaceIocContainer
- 複合屬性名
當設定
bean
屬性的時候,我們可以使用複合或嵌入屬性名,隻要這個
path
(點式引用)下面所有元件期望這個最終屬性名不為
null
。考慮下面的
bean
定義:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
這個
something
bean有一個
fred
屬性,
fred
有一個
bob
bob
擁有一個
sammy
屬性并且最終
sammy
屬性被設定值為
123
。為了能正常工作,
something
的屬性
fred
fred
bob
在這個
bean
構造之前必須不能為
null
。否則,會抛出一個
NullPointerException
1.4.3 使用
depends-on
如果
bean
依賴其他
bean
,也就是意味着bean需要設定依賴的
bean
屬性。典型地,我們可以基于XML配置中繼資料使用
ref
去完成。然而,一些bean之間的依賴不是直接的。一個例子是在類中一個靜态的初始化器需要被觸發,例如:資料庫驅動注冊。
depends-on
屬性能夠顯示地強制一個或多個
bean
在依賴
bean
初始化之前初始化。下面的例子使用
depends-on
屬性去表達對一個簡單
bean
的依賴。
<!--beanOne依賴manager-->
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
為了表達多個bean的依賴,提供一個bean名稱的集合清單作為
depends-on
屬性值(
,
;
空格
是有效的分隔符)。
<!--beanOne依賴manager,accountDao-->
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
僅僅在 單例bean 場景下,屬性能夠指定初始化時間依賴和對應的銷毀時間依賴。與給定bean定義依賴關系的從屬bean首先被銷毀,然後再銷毀給定bean本身(備注:被依賴的bean先銷毀,在銷毀宿主bean)。是以,
depends-on
可以控制關閉順序。
depends-on
com.liyong.ioccontainer.starter.XmlDependOnSpaceIocContainer
1.4.4 bean的懶加載
預設情況下,
ApplicationContext
實作更早的建立和配置所有的
單例bean
作為初始化過程的一部分。通常地,這個前置初始化是可取的,因為錯誤的配置或環境變量被立即的發現,而不是幾個小時甚至幾天後才被發現。當這個行為不是可取的時候,我們可以通過标記
bean
作為一個懶加載的單例
bean
去阻止提前初始化。一個懶加載
bean
告訴容器當第一次請求的時候去建立執行個體而不是在容器啟動時候。
在XML配置中,這個行為通過在元素的屬性
lazy-init
控制的。下面例子展示:
<!--設定bean延遲初始化 注意:Spring中的bean預設是單例的-->
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
目前面的配置通過
ApplicationContext
加載并啟動時,這個
lazy
bean沒有被提前的初始化,而
not.lazy
bean被盡早的初始化。
然而,當一個懶加載bean是另一個單例
bean
的依賴時候,這個懶加載不是懶加載的。
ApplicationContext
在啟動時建立這個懶加載
bean
,因為它必須滿足這個單例
bean
的依賴。這個懶加載
bean
被注入到一個單例
bean
是以它不是懶加載的。
我們也可以在容器級别通過使用元素的
default-lazy-init
屬性控制懶加載,下面例子展示怎樣使用:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
com.liyong.ioccontainer.starter.XmlLazyInitilaizedIocContainer
1.4.5自動裝配協調者
Spring容器能自動裝配協調者bean之間的關系。通過檢查
ApplicationContext
的内容,Spring自動為你的bean解析協同者(其他依賴bean)。自動裝配有下面的優勢:
- 自動裝配能顯著的降低對屬性和構造函數參數的需要。(其他機制例如:在這方面,其他機制(例如本章其他地方讨論的bean模闆)也很有價值)。
- 自動裝配可以随着對象的演化而更新配置。例如:如果你需要添加一個類的依賴,依賴能夠被自動地被滿足不需要修改配置。是以,自動裝配在開發過程中特别有用,當代碼庫變得更加穩定時,自動裝配可以避免切換到顯式連接配接的選項。
當使用基于XML中繼資料配置,我們可以為這個
bean
指定自動裝配模式通過元素的
autowire
屬性。自動裝配有4種模式。你可以對每個bean指定自動裝配是以可以選擇自動裝配哪些bean。下面的表格描述了4種裝配模式:
Mode | Explanation |
---|---|
| 預設不自動裝配. 引用必須通過 定義. 對于較大的部署,建議不要更改預設設定,因為明确指定協同者可以提供更好的控制和清晰度。 在某種程度上,它記錄了系統的結構。 |
| 通過屬性名自動裝配。 查找一個 與自動裝配屬性名相同的 名字。例如:如果bean定義是被設定為通過名字自動注入并且包含了一個 屬性(也就是,有一個 方法), 名字的 并且使用它設定到屬性上。 |
| 如果在容器中和屬性具有相同類型的唯一 存在會被自動注入到屬性。如果有多個 存在,一個緻命的異常被抛出,表示你不能使用 為bean自動裝配。如果沒有比對的 ,不發生任何事情(屬性不被設定)。 |
| 類似于 ,但是使用構造函數參數。如果在容器中沒有一個 被比對到會抛出一個緻命的 |
使用
byType
或構造函數自動裝配模式,你可以結合數組和類型化的集合,在這種情況下,提供容器中與期望類型比對的所有自動裝配候選,以滿足相關性。如果期望
key
的 類型是
String
,你可以自動裝配強類型的
Map
執行個體。一個自動裝配
Map
執行個體的值由所有比對期望類型執行個體組成,并且這個Map執行個體的這些
key
與
bean
名稱對應。
- 自動裝配的優勢和限制
在一個系統中一緻地使用自動裝配将工作的更好。如果通常不使用自動裝配,則可能使開發人員僅使用自動裝配來連接配接一個或兩個bean定義而感到困惑。
考慮自動裝配的限制和優勢:
-
property
中顯示依賴設定總是覆寫自動裝配。你不能自動裝配簡單的屬性例如:原生類型,constructor-arg
String
(簡單屬性數組)。這種限制是由設計造成的。Class
- 自動裝配沒有顯示裝配精确。盡管如前面的表中所述,
小心地避免猜測,以免産生可能産生意外結果的歧義。Spring
管理對象之間的關系不再明确記錄。Spring
- 裝配資訊可能對從
容器生成文檔的工具不可用。Spring
- 容器中的多個
定義可能與要自動裝配的bean
方法或構造函數參數指定的類型相比對。對于數組、集合或setter
執行個體,這不一定是問題。然而,為了依賴期望一個簡單值,這種歧義不會被任意解決(意思是期望一個Map
容器中确有多個比對的bean
)。如果沒有唯一的有效bean
的定義會抛出一個異常。bean
在最後的場景中,你有一些可選項:
- 顯示的裝配放棄自動裝配。
- 通過設定
定義的bean
屬性為autowire-candidate
去避免自動裝配,在 下一個章節 描述。false
- 通過設定元素屬性
primary
指定一個true
定義作為主要的候選者。bean
- 實作更細粒度的有效控制通過基于注解的配置,在 基于注解容器配置 中描述。
- 自動裝配排除bean
在每個bean的基礎上,你可以從自動裝配中排除一個
bean
。在
Spring
的XML格式中,設定元素的
autowire-candidate
false
。容器使該特定的
bean
定義不适用于自動裝配基礎結構(包括注釋樣式配置,例如@Autowired)。
autowire-candidate屬性被設計僅僅通過基于類型自動裝配有影響。它不會影響通過名字來顯示引用的方式,即使這個指定bean沒有被标記作為一個自動裝配候選者這個名字也會被解析。是以,如果名稱比對,按名稱自動裝配仍然會注入Bean。
可以基于與
Bean
名稱的模式比對來限制自動裝配候選者。頂層元素接受一個或多個表達式在
default-autowire-candidates
屬性中。例如,去限制自動裝配候選者任意狀态的
bean
,它的名字以
Repository
結尾,提供一個值為
*Repository
的表達式。提供多個表達式可通過
;
号分割。為一個
bean
autowire-candidate
屬性顯示指定
true
false
總是優先級最高(比如
default-autowire-candidates
優先級高),指定的規則被覆寫。
當這些
bean
不想自動裝配注入到其他
bean
中時,這些技術是非常有用的。這并不意味着一個被排除的
bean
本身不能通過使用自動裝配來配置。而是,
bean
本身不是一個候選者不會被注入到其他
bean
中。
com.liyong.ioccontainer.service.AutowireCandidateService
1.4.6 方法注入
在大多數應用場景中,在容器中大多數
bean
是單例的。當單例
Bean
需要與另一個單例
Bean
協作或非單例
Bean
需要與另一個非單例
Bean
協作時,典型的處理依賴通過定義一個
bean
作為其他
bean
的屬性。當
bean
的生命周期不同時會出現問題。假設單例bean
A
需要使用非單例bean
B
(原型),假設
A
的每個方法被調用。這個容器僅僅建立單例bean
A
一次并且隻有一次機會去設定屬性。容器無法每次為bean
A
提供一個新的bean
B
執行個體(單例A每次從容器擷取bean
B
不能每次提供一個新
bean
)。
一個解決方案時放棄控制反轉。我們也可以通過實作
ApplicationContextAware
接口讓bean
A
意識到容器。并在bean
A
每次需要bean
B
時,通過使用
getBean("B")
擷取一個新執行個體
bean
。下面例子展示使用方式:
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// 通過從容器擷取bean
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
//容器注入ApplicationContext
this.applicationContext = applicationContext;
}
}
上面的例子不是可取的,因為這個業務代碼需要容器回調耦合了
Spring
架構(備注:這個我不敢苟同,上面我也發表了觀點)。方法注入,
Spring IoC
容器進階特性,讓我們處理這種場景更簡潔。(備注:上面
Command
配置為原型才能達到效果)
可以閱讀更多關于方法注入的動機在 部落格入口
- 查找方法注入
查找方法注入是容器覆寫容器管理
bean
上的方法并傳回容器中另一個命名
bean
的查找結果的能力。在前面描述的場景中,典型地查找涉及到原型bean。
Spring
架構通過
CGCLB
庫去動态地生成一個子類去覆寫這些方法以實作方法注入。
- 為了動态子類能夠正常工作,
不能是
Spring bean
并且方法也不能是
final
final
- 單元測試一個具有抽象方法的類需要你自己去子類化并且提供一個抽象方法的存根實作。
- 元件掃描也需要具體方法,這需要具體的類别。
- 進一步關鍵限制是方法查找不能在工廠方法并且特别在
類被
configuration
标注的方法,因為,在這種情況,容器不負責建立執行個體,是以無法即時建立運作時生成的子類(因為這種方法
@Bean
是由我們自己建立處理的容器不能控制
Bean
的生成)。
bean
在前面的代碼片段
CommandManager
中,
Spring
容器動态的覆寫這個
createCommand
方法的實作。
CommandManager
類沒有任何的
Spring
的依賴,重構如下:
package fiona.apple;
// 沒有Spring的依賴!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在用戶端類中包含被注入的方法(在這個例子中是
CommandManager
類),這個方法被注入要求一個下面格式的簽名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果這個方法是
abstract
,動态地生成子類實作這個方法。除此之外,動态生成子類覆寫在源類中具體方法定義。考慮下面例子:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="myCommand"/>
</bean>
每當需要新的
myCommand
bean
執行個體時,辨別為
commandManager
的bean就會調用其自己的
createCommand()
方法。你必須非常的小心應用
myCommand bean
作為一個原型,如果這是真實需要的。如果這個
bean
是單例的,
myCommand
執行個體每次都傳回同一個
bean
或者,在基于注解元件模式中,你可以聲明一個查找方法通過
@Lookup
注解,像下面例子:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
//指定名稱查找,在容器中查找
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更習慣地說,你可以依靠針對查找方法的聲明傳回類型來解析目标
Bean
:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
//沒有指定名稱查找,通過查找方法的MyCommand類型去容器查找
@Lookup
protected abstract MyCommand createCommand();
}
注意,通常應該使用具體的存根實作聲明此類帶注解的查找方法,為了使它們與
Spring
的元件掃描規則相容,預設情況下,抽象類将被忽略。此限制不适用于顯式注冊或顯式導入的
Bean
類。
擷取不同範圍的目标bean的其他方式是/
ObjectFactory
注入點。檢視 Scoped Beans as Dependencies
Provider
.
你也可以找到
(在
ServiceLocatorFactoryBean
包)去使用。
org.springframework.beans.factory.config
com.liyong.ioccontainer.starter.XmlLookUpInjectionIocContainer
com.liyong.ioccontainer.starter.XmlLookUpInjectionByAnnotaionIocContainer
- 任意方法替換
與查找方法注入相比,方法注入的一種不太有用的形式是能夠用另一種方式實作替換托管
bean
中的任意方法。你可以放心地跳過本節的其餘部分,直到你真正需要此功能為止。
基于XML元素資料配置,你可以使用
replaced-method
元素将現有的方法實作替換為已部署的
Bean
。考慮下面的類,這個類有一個叫做
computeValue
的方法我們想去覆寫這個方法。
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
類實作
org.springframework.beans.factory.support.MethodReplacer
接口提供新的方法定義,像下面定義:
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
bean
的定義去部署到源類并且指定方法覆寫類似如下例子:
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- 替換方法 -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
你可以使用一個或多個在元素中去訓示這個被覆寫的方法簽名。僅當方法重載并且類中存在多個變體時,才需要對參數進行簽名。
為了友善起見,參數的類型字元串可以是完全限定類型名稱的子字元串。例如:
java.lang.String
java.lang.String
String
Str
因為參數的數量通常足以區分每個可能的選擇,是以通過讓你僅輸入與參類型比對的最短字元串,此快捷方式可以節省很多輸入。
com.liyong.ioccontainer.starter.XmlMethodReplaceIocContainer
作者
個人從事金融行業,就職過易極付、思建科技、某網約車平台等重慶一流技術團隊,目前就職于某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大資料、資料存儲、自動化內建和部署、分布式微服務、響應式程式設計、人工智能等領域。同時也熱衷于技術分享創立公衆号和部落格站點對知識體系進行分享。
部落格位址:
http://youngitman.techCSDN:
https://blog.csdn.net/liyong1028826685微信公衆号:

技術交流群: