[TOC]
### 依賴
典型的企業應用不會單一得由一個對象組成(或者說Spring術語中的bean)。即便是最簡單的系統也需要多個對象共同協作來展示給終端用戶一個條理分明的應用。接下來的這一節內容會闡述如何定義多個獨立于應用程序的bean一起協同工作完成目標。
#### 1.4.1. 依賴注入
依賴注入 (DI) 是指對象之間的依賴關系,也就是說,一起協作的其他對象只通過構造器的參數、工廠方法的參數或者由構造函數或者工廠方法創建的對象設置屬性。因此容器的工作就是創建bean并注入那些依賴關系。這個過程實質通過直接使用類的構造函數或者服務定位模式來反轉控制bean的實例或者其依賴關系的位置,因此它有另外一個名字叫控制反轉 (IoC)。
運用了DI原理代碼會更加清晰并且由依賴關系提供對象也將使各層次的松耦合變得更加容易。對象不需要知道其依賴關系,也不需要知道它的位置或者類之間的依賴。因此,你的類會更容易測試,尤其是當依賴關系是在接口或者抽象基本類,
DI主要有兩種注入方式,即構造器注入和Setter注入。
##### 構造器注入
基于構造器注入 DI通過調用帶參數的構造器來實現,每個參數代表著一個依賴關系。此外,還可通過給 靜態 工廠方法傳參數來構造bean。接下來的介紹將認為給構造器傳參數和給靜態工廠方法傳參數是類似的。下面展示了只能使用構造器來注入依賴關系的例子。請注意這個類并沒有什么 特別 之處,它只是個普通的POJO,不依賴于特殊的接口,抽象類或者注解。
~~~Java
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
~~~
###### 構造器參數解析
構造器參數通過參數類型進行匹配。如果構造器參數的類型定義沒有潛在的歧義,那么bean被實例化的時候,bean定義中構造器參數的定義順序就是這些參數的順序并依次進行匹配。看下面的代碼:
~~~Java
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
~~~
上述代碼中參數類型定義不存在潛在的歧義,我們假設Bar和Baz之間不存在繼承關系。因此,下面代碼中在元素<constructor-arg/>的配置即使沒有明確指定構造參數順序或者類型也會起作用。
~~~xml
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
~~~
當另一個bean被引用,它的類型是已知的,并且匹配也沒問題(跟前面的例子一樣)。當我們使用簡單類型,比如\<value>true\</value>。Spring并不能知道該值的類型,不借助其他幫助Spring將不能通過類型進行匹配。看下面的類:
~~~Java
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屬性來顯式指定那些簡單類型那個的構造參數類型,比如:
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
~~~
使用index屬性來顯式指定構造參數的索引,比如:
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
~~~
使用索引可以解決多個簡單值的混淆,還能解決構造方法有兩個相同類型的參數的混淆問題,注意index是從0開始的。
你也可以使用構造器參數命名來指定值的類型:
~~~xml
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
~~~
請記住為了使這個起作用,你的代碼編譯時要打開編譯模式,這樣Spring可以檢查構造方法的參數。如果你不打開調試模式(或者不想打開),也可以使用 @ConstructorProperties JDK注解明確指出構造函數的參數。下面是簡單的例子:
~~~Java
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
~~~
##### Setter注入
在調用了無參的構造方法或者無參的靜態工廠方法實例化bean之后,容器通過回調bean的setter方法來完成setter注入。接下來的例子將展示只使用setter注入依賴。這個類是個普通的Java類
~~~Java
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
~~~
ApplicationContext所管理的beans支持構造函數注入和setter注入,在一些依賴已經使用構造器注入之后它還支持setter注入。你可以以BeanDefinition的形式配置依賴,它能根據指定的PropertyEditor實現將屬性從一種格式轉化為另外一種格式。但是,大多數Spring的使用者不會直接使用這些類(也就是通過編程的形式),而是采用XML配置這些bean,注解的組件(即用@Component,@Controller等注解類),或者基于@Configuration類的@Bean方法。本質上這些資源會轉換成BeanDefinition的實例并且用于加載整個Spring IoC容器實例。
>:-: **構造器注入還是Setter注入**
>
>因為你可以混合使用構造器注入和setter注入, 強制性依賴關系 時使用構造器注入, 可選的依賴關系 時使用setter方法或者配置方法是比較好的經驗法則。請注意@Required注解在setter方法上可以注入所需要的依賴。
>
>Spring開發團隊一般主張當實現的應用組件是不可變對象時使用構造器注入并且要保證所需的依賴不是null。此外構造器注入的組件總是返回給客戶端(或調用)完整的初始化狀態。另一方面,大量的構造器參數造成糟糕的代碼異味,這表明類可能承擔了太多的職責應該需要重構以便更好的適當分離要解決的問題。
>
>setter注入主要只用作可選的依賴,這些依賴分配合理的缺省值。否則,當代碼使用依賴時必須進行非空檢查。setter注入的一個好處是setter方法使得這個類的對象在以后的某個時候還可合理的重新配置或者重新注入。JMX MBeans的管理就是一個很好的setter注入例子。
使用DI的風格可以使特定的類更有意義。有時,使用第三方類時你并沒有資源,那么這個選擇很適合你。舉個例子,如果第三方類并不公開任何setter方法,那么構造器注入可能是唯一可用的依賴注入方式。
###### 依賴解決步驟
容器解決依賴問題通常有以下幾個步驟:
描述所有bean的ApplicationContext創建并根據配置的元數據初始化。配置的元數據可以通過置頂的XML,Java代碼或者注解。
* 每個bean的依賴將以屬性,構造器參數或者靜態工廠方法的形式出現。當這些bean被創建時,這些依賴將會提供給該bean。
* 每個屬性或者構造器參數既可以是一個實際的值也可以是容器中的另一個引用。
* 每個指定的屬性或者構造器參數值必須能夠被轉換成特定的格式或構造參數所需的類型。默認情況下Spring會以String類型轉換為各種內置類型,比如int,long, String, boolean 等。
當Spring容器創建時容器會校驗每個bean的配置。但是在bean被實際創建 前,bean的值并不會被設置。那些單例類型的bean和被設置為預安裝(默認)的bean會在容器創建時與容器同時創建。Scopes是在Section 5.5, “Bean作用域”中定義的。同時,另外的bean只會在被需要時創建。伴隨著bean被實際創建,作為該bean的依賴和它的依賴的依賴(以此類推)會被創建和分配。注意 這些依賴之間的解決會顯示遲一些.
>:-: **循環依賴**
>
>如果你主要使用構造器注入,很有可能會產生無法解決的循環依賴問題。 舉個例子:A類需要通過構造器注入B類的實例,并且B類又需要通過構造器注入A類的實例。如果為類A和類B配置的bean被相互注入的話,Spring IoC容器在運行時會檢測到這個循環依賴并且拋出一個BeanCurrentlyInCreationException異常。
>
>一個可能的解決方法是修改類的源代碼,將構造器注入改為setter注入。或者只使用setter注入避免使用構造器注入。換句話說,雖然這并不被推薦使用,你可以使用setter注入配置循環依賴。 和通常的情況不同(沒有循環依賴),bean A 和bean B之間的循環依賴將會導致其中一個bean在被完全初始化的之前被注入到另一個bean里(先有雞先有蛋的問題)
通常你可以信賴Spring。在容器加載時Spring會檢查配置,比如不存在的bean和循環依賴。當bean創建時,Spring盡可能遲得設置屬性和依賴關系。這意味著即使Spring正常加載,在你需要一個存在問題或者它的依賴存在問題的對象時,Spring會報出異常。舉個例子,bean因設置缺少或者無效的屬性會拋出一個異常。因為一些配置問題存在將會導致潛在的可見性被延遲,所以默認ApplicationContext的實現bean采用提前實例化的單例模式。在實際需要之前創建這些bean會帶來時間和內存的開銷,當ApplicationContext創建完成時你會發現配置問題,而不是之后。你也可以重寫默認的行為使得單例bean延遲實例化而不是提前實例化。
如果不存在循環依賴,當一個或者多個協助bean會被注入依賴bean時,每個協助bean必須在注入依賴bean之前 完全 配置好。這意味著如果bean A對bean B存在依賴關系, 那么Spring Ioc容器在調用bean A的setter方法之前會完全配置bean B。換句話說,bean會被實例化(如果不是采用提前實例化的單例模式),相關的依賴會被設置好,相關的lifecycle方法(比如configured init 方法或者InitializingBean callback 方法)會被調用
###### 一些依賴注入的例子
接下來的Setter注入例子使用基于XML的配置元數據的方式。相應的Spring XML配置文件:
~~~xml
<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="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
~~~
~~~Java
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里的屬性,接下來的例子會使用構造器注入:
~~~xml
<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="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
~~~
~~~Java
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調用靜態工廠方法來返回對象的實例:
~~~xml
<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"/>
~~~
~~~Java
public class ExampleBean {
// a private constructor
private ExampleBean(...) {
...
}
// 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.
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
~~~
靜態工廠方法參數由<constructor-arg/>元素提供,實際上這和使用構造器是一樣的。工廠方法 返回的類的類型并不一定要與包含靜態工廠方法的類類型一致,雖然在這個例子中是一樣的。 實例工廠方法(不是靜態的)與此相同(除了使用factory-bean屬性代替class屬性外),所以這里不作詳細討論。
#### 1.4.2 依賴配置詳解
正如在前面章節所提到的,你可以定義bean的屬性和構造器參數作為其他所管理的bean的依賴(協作), 或者是內聯的bean。基于XML的Spring配置元數據支持使用\<property/> 和 \<constructor-arg/>元素定義。
##### 直接變量 (基本類型, String類型等)
\<property/>元素的value值通過可讀的字符串形式來指定屬性和構造器參數。Spring的conversion service 把String轉換成屬性或者構造器實際需要的類型。
~~~xml
<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配置
~~~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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<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更加簡潔;但是,編碼的錯誤只有在運行時才會被發現而不是編碼設計的時候,除非你在定義bean的時候, 使用 IntelliJIDEA 或者 Spring Tool Suite (STS) 支持動態屬性補全的IDE。IDE的幫助是非常值得推薦的。
你也可以配置java.util.Properties實例,就像這樣:
~~~xml
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a 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把元素\<value/>內的文本轉換為java.util.Properties 實例。由于這種做法非常簡單,所以這是Spring團隊在很多地方采用內嵌的\<value/>元素代替value屬性。
##### idref元素
idref元素用來將容器內其他bean的id(值是字符串-不是引用)傳給元素\<constructor-arg/> 或者 \<property/>
~~~xml
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean" />
</property>
</bean>
~~~
上面的bean定義片段完全等同于(在運行時)下面片段:
~~~xml
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean" />
</bean>
~~~
第一種形式比第二種形式更好,因為使用idref標簽允許容器在部署時驗證引用的bean是否存在。 在第二種形式中,傳給 client bean中屬性targetName的值并沒有被驗證。 只有當 client bean完全實例化的時候錯誤才會被發現(可能伴隨著致命的結果)。如果 client bean是原型 bean。那么這個錯誤和異常可能只有再容器部署很長一段時間后才能被發現。
>[info] idref元素上的local屬性在4.0之后不再支持,因為它不再提供普通bean的價值。當你升級到4.0 schema時需要修改存在的idref local為idref bean
與ProxyFactoryBean bean定義中使用\<idref/>元素指定AOP 攔截器配置 (版本不低于Spring 2.0)相同之處在于:當你指定攔截器名稱的時候使用<idref/>元素可以防止你拼錯攔截器的id。
##### 引用其他bean(協作者)
在 \<constructor-arg/> 或者 \<property/>可以使用ref 元素。該元素用來將bean中指定屬性的值設置為對容器的另外一個bean(協作者)的引用。 該引用bean將被作為依賴注入,而且再注入之前會被初始化(如果協作者是單例)。所有的引用最終都是另一個對象的引用。 bean的范圍和驗證依賴于你指定的bean,local,或者 parent屬性的id/name。
通過<ref/>標簽的bean屬性定義目標bean是最常見的形式,通過該標簽可以引用同一容器或者父容器任何bean,無論是否在 相同的xml文件中。xml的bean元素的值既可以目標bean的id屬性也可以是其中一個目標bean的name 屬性值。
~~~xml
<ref bean="someBean"/>
~~~
通過parent屬性指定目標bean來創建bean的引用,該bean是當前容器下的父級容器。parent屬性值既可以是目標bean的id也可以是 name屬性值。而且目標bean必須在當前容器的父級容器中。使用parent屬性的主要用途是為了用某個與父級容器中的bean同名的代理來包裝父級容器中的一個bean。
~~~xml
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
~~~
~~~xml
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
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>
~~~
>[info]ref元素上的local屬性4.0 beans xsd之后不在支持,因為它能提供的值不比普通bean多。當你升級到4.0schema時需要修改存在的ref local 為ref bean。
##### 內部bean
所謂的內部bean就是指在 \<property/> 或者 \<constructor-arg/> 元素內部使用\<bean/>定義bean。
~~~xml
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
~~~
內部bean的定義不需要id或者name屬性。容器會忽略這些屬性值。同時容器也會忽略scope標志位。內部bean 總是 匿名的 并且他們總是伴隨著外部bean創建。同時將內部bean注入到包含該內部bean之外的bean是不可能的。
##### 集合
在\<list/>, \<set/>, \<map/>, 和\<props/>元素中,你可以設置值和參數分別對應Java的集合類型List, Set, Map, 和 Properties
~~~xml
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- 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>
<!-- 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>
<!-- 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或者value值,或者set的value值還可以是以下任意元素:
~~~
bean | ref | idref | list | set | map | props | value | null
~~~
###### 集合合并
Spring容器也支持集合的 合并。開發者可以定義parent-style \<list/>, \<map/>, \<set/> 或者\<props/> 元素, 并定義child-style 的\<list/>, \<map/>, \<set/> 或者 \<props/>元素繼承和覆蓋自父集合。也就是說。父集合元素合并后的值就是子集合的最終結果,而且子集中的元素值將覆蓋父集中對應的值。
關于合并的章節涉及到了parent-child bean機制。不熟悉父子bean的讀者可參見relevant section.
接下來的例子展示了集合的合并:
~~~xml
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</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">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
~~~
注意child bean的定義中\<props/>元素上的merge=true屬性的用法。當child bean被解析并且被容器初始化,產生的實例包含了adminEmails ,Properties集合,其adminEmails將與父集合的adminEmails屬性進行合并。
~~~
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
~~~
子bean的Properties集合將從父\<props/>集成所有屬性元素。同時子bean的support值將覆蓋父集合的相應值。
\<list/>, \<map/>, 和 \<set/>集合類型的合并處理都基本類似。\<list/>元素某個方面有點特殊,這和List集合類型的語義學有關 換句話說,比如維護一個有序集合的值,父bean的列表內容將排在子bean列表內容的前面。對于Map, Set和 Properties集合類型沒有順序的概念, 因此作為相關的Map, Set, 和 Properties實現基礎的集合類型在容器內部排序的語義。
**集合合并的限制**
你不能合并兩種不能類型的集合(比如Map 和 List),如果你這么做了將會拋出相應的異常。merge屬性必須在繼承 的子bean中定義。定義在父bean的集合屬性上指定的merge屬性是多余的并且得不到期望的合并結果。
**強類型集合**
Java 5 引入了泛型,這樣你可以使用強類型集合。換句話說繩命一個只能包含String類型元素的Collection是可能的(比如)。 如果使用Spring來給bean注入強類型的Collection,你可以利用Spring的類型轉換,在向強類型Collection添加元素前,這些元素將被轉換。
~~~java
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
~~~
~~~xml
<beans>
<bean id="foo" class="x.y.Foo">
<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>
~~~
當foo bean的accounts 屬性準備注入時,通過反射獲得強類型Map<String, Float>元素類型的泛型信息。Spring的底層類型轉換 機制會把各種value元素值轉換為Float,因此字符串9.99, 2.75 和3.99將會轉換為實際的Float 類型。
###### Null和空字符串
Spring會把空屬性當做空字符串處理。以下的基于XML配置的片段將email屬性設置為空字符串。
~~~xml
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
~~~
先前的例子等同于以下Java代碼:
~~~Java
exampleBean.setEmail("")
~~~
\<null/>元素處理null值,例如:
~~~xml
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
~~~
上面的配置等同于下面的Java代碼:
~~~Java
exampleBean.setEmail(null)
~~~
###### XML使用p命名空間簡化
使用p命名空間可以用bean 元素的屬性代替\<property/>元素來描述屬性值或者協作bean。
Spring支持名稱空間的可擴展配置with namespaces,這些名稱空間基于一種XML Schema定義。這章節涉及到的beans 配置都是定義在一個XML Schema文檔理。但是p命名空間不是定義在XSD文件而是存在于Spring內核中。
下面的例子展示了兩種XML片段,其結果是一樣的:第一個使用了標準XML格式,第二種使用了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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="foo@bar.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="foo@bar.com"/>
</beans>
~~~
在例子中,使用p命名空間的bean定義有了一個叫email的屬性。這告訴Spring要包含這個屬性的聲明。正如前面所說的, p命名空間不需要schema定義,因此你可以設置屬性的名字作為bean的property的名字。
接下來的例子包括了兩種以上bean的定義,都引用了另外一個bean。
~~~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
http://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命名空間包含了一個屬性值,而且使用了一個特殊的格式聲明了一個屬性的引用。在第一個bean 定義中使用了<property name="spouse" ref="jane"/>創建一個john bean 對jane bean的引用,第二個bean的定義使用了p:spouse-ref="jane",它們做了同樣一件事情。在這個例子中spouse是屬性名,而-ref部分聲明了這不是一個直接的值而是另一個bean的引用。
>[info]p命名空間沒有標準XML格式那么靈活。舉個例子,聲明屬性的引用是以Ref結尾的,采用p命名空間將會產生沖突,但是采用標準XML 格式則不會。我們建議你小心的選擇并和團隊成員交流你的想法,避免在XML文檔中同時使用所有的三種方法。
###### XML使用c命名空間簡化
和“XML使用p命名空間簡化”類似,Spring3.1 引入了c命名空間,使用內聯的構造參數代替嵌套的constructor-arg元素
讓我們回顧一下the section called “構造器注入”使用c命名空間的例子:
~~~xml
<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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
<!-- traditional declaration -->
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
<constructor-arg value="foo@bar.com"/>
</bean>
<!-- c-namespace declaration -->
<bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>
</beans>
~~~
和p命名空間約定的一樣(bean的引用以-ref結尾),c命名空間使用它們的名稱作為構造器參數。同時它需要聲明即使它沒有 XSD schema中定義(但是它存在于Spring內核中)
極少數的情況下構造器參數的名稱不可用(通常字節碼沒有經過調試信息編譯),可以使用備份進行參數索引。
~~~xml
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
~~~
>[info]因為XML的語法,索引標記需要_主導作為XML屬性名稱,這不能已數字開頭(即使某些IDE這么允許)
在實踐中,構造器解析機智機制在匹配參數上相當高效。除非你需要這么做,我們建議您在配置中使用符號名稱。
###### 組合屬性名稱
當你設置bean屬性時可以使用組合或者嵌套屬性名稱,只要路徑上所有組件除了最終屬性不為空。看以下bean的定義:
~~~xml
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
~~~
foo bean有一個fred屬性,fred屬性又有個bob屬性,bob屬性又有個屬性,最后把 sammy屬性設置值為123。為了是這個起作用,foo的fred屬性和 fred的bob屬性在bean被構造后都不能為空,否則會拋出NullPointerException異常。
#### 1.4.3. 使用 depends-on
如果一個bean是另外一個bean的依賴,這通常意味著這個bean可以設置成為另外一個bean的屬性。在XML配置文件中你可以使用<ref/> element實現依賴。但是某些時候bean之間的依賴并不是那么直接。舉個例子:類的靜態塊初始化,比如數據庫驅動的注冊。depends-on屬性 可以同于當前bean初始化之前顯式地強制一個或多個bean被初始化。下面的例子中使用了depends-on屬性來指定一個bean的依賴。
~~~xml
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
~~~
為了實現多個bean的依賴,你可以在depends-on中將指定的多個bean名字用分隔符進行分隔,分隔符可以是逗號,空格以及分號等。
~~~xml
<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" />
~~~
>[info]depends-on屬性在bean的定義中可以指定初始化時的依賴和指定相應的銷毀時的依賴,該依賴只針對于singleton bean 這樣depends-on可以控制銷毀順序。
#### 1.4.4. 延遲初始化bean
ApplicationContext實現的默認行為就是再啟動時將所有singleton bean提前進行實例化。 通常這樣的提前實例化方式是好事,因為配置中或者運行環境的錯誤就會被立刻發現,否則可能要花幾個小時甚至幾天。如果你不想 這樣,你可以將單例bean定義為延遲加載防止它提前實例化。延遲初始化bean會告訴Ioc容器在第一次需要的時候才實例化而不是在容器啟動時就實例化。
在XML配置文件中,延遲初始化通過<bean/>元素的lazy-init屬性進行控制,比如:
~~~xml
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
~~~
當ApplicationContext實現上面的配置時,設置為lazy的bean將不會在ApplicationContext啟動時提前實例化,而`not.lazy`bean 卻會被提前實例化。
但是當一個延遲加載的bean是單例bean的依賴,但這個單例bean又不是 延遲加載,ApplicationContext在啟動時創建了延遲加載 的bean,因為它必須滿足單例bean的依賴。因此延遲加載的bean會被注入單例bean,然而在其他地方它不會延遲加載。
你也可以使用<beans/>元素上的default-lazy-init屬性在容器層次上控制延遲加載。比如:
~~~xml
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
~~~
#### 1.4.5. 自動裝配協作者
Spring容器可以自動裝配相互協作bean的關聯關系。因此,如果可能的話,可以自動讓Spring檢測ApplicationContext的內容自動 處理協作者(其他bean)。自動裝配有以下好處:
* 自動裝配可以顯著得減少指定屬性或者構造器參數的需求。(其他的機制比如bean模板discussed elsewhere in this chapter在這方面也是由價值的)
* 當對象發生變化時自動裝配可以更新配置。比如如果你需要給一個類添加依賴,那么這個依賴可以被自動滿足而不需要你去修改配置。 因此自動依賴在開發時尤其有用,當系統趨于穩定時改為顯式裝配。
當使用XML配置元數據,可以使用\<bean/>元素的autowire屬性 為定義的bean指定自動裝配模式。你可以指定自動裝配per bean,選擇那種方式來自動裝配。
*Table 自動裝配模式*
| Mode | Explanation 模式解釋 |
| --- | --- |
| no | (默認)不自動裝配。Bean的引用必須用ref元素定義。對于較大的部署不建議改變默認設置,因為明確指定協作者能更好控制和維護系統。 在某種程度上,它記錄了系統的結構。 |
| byName | 通過屬性名稱自動裝配。Spring會尋找相同名稱的bean并將其與屬性自動裝配。譬如,如果bean的定義設置了根據名稱自動裝配, 并且包含了一個master 屬性(換句話說,它有setMaster(..)方法),Spring會尋找名為master的bean的定義,并用它來裝配屬性 |
| byType | 如果容器中存在一個與指定屬性類型相同的bean,那么將與該屬性自動裝配。如果存在多個該類型的bean,將會拋出異常,并指出 不能使用byType自動裝配這個bean。如果沒有找到相同類型的,什么也不會發生。屬性不會被設置。|
| constructor | 和byType類似,不同之處在于它應用于構造器參數。如果在容器中沒有找到與構造器參數類型一致的bean,就會拋出異常。 |
byType 或者 constructor 自動裝配模式也可以應用于數組和指定類型的集合。在這種情況下容器中的所有匹配的自動裝配對象將 被應用于滿足各種依賴。對于key值類型為String的強類型Map也可以自動裝配。一個自動裝配的Map value值將由所匹配類型的bean所填充。
你可以結合自動裝配和依賴檢查,后者將會在自動裝配完成之后進行。
##### 自動裝配的局限性和缺點
在工程里一致使用自動裝配,這將會工作得很好。如果自動裝配并不常使用,只使用在一個或兩個bean的定義上, 它可能會對開發者產生困擾。
考慮一下自動裝配的局限性和缺點:
* property 和 constructor-arg 顯式的依賴設置總是會覆蓋自動裝配。你不能裝配所謂的簡單屬性比如原始的Strings, 和 Classes (這樣的簡單屬性數組也是)。這種缺陷是故意設計的。
* 自動裝配沒有顯示編寫精確。雖然在上面的表格提到的,Spring很小心得避免猜測模糊的情況,這可能會導致意想不到的結果。 Spring管理的對象之間的關系不再記錄明確。
* 自動裝配的依賴信息可能不能用于根據Spring容器生成文檔的的工具。
* 在容器內部可能存在多個bean的定義與自動裝配的setter方法或者構造器參數匹配。對于數組,集合或者Map來說,這不是問題。 但是對于單值依賴來說,就會存在模棱兩可的問題。如果bean定義不唯一,裝配時就會拋出異常。
針對于上述場景,你會有多個選項:
* 放棄自動裝配以便于明確依賴關系。
* 在bean定義中通過設置autowire-candidate屬性為false避免該bean自動裝配,這將會在下一節中詳細描述。
* 在bean定義中設置\<bean/>元素上的`primary`屬性為true,將該bean設置為首選自動裝配bean。
* 使用注解配置實現更加細粒度的控制,詳情見**Annotation-based container configuration**。
##### 將bean排除在自動裝配之外
在提前實例化bean的基礎上,你可以將bean排除在自動裝配之外。在Spring XML格式中,將\<bean/> 元素中的autowire-candidate屬性 設置為false。容器會使特定的bean定義不可于自動裝配(包括注解配置比如@Autowired)
你也可以對使用bean名字進行模式匹配來對自動裝配進行限制。頂層的<beans/> 元素在它的default-autowire-candidates屬性 接受一個或多個模式。譬如,為了限制
對于那些從來就不會被其他bean采用自動裝配的方式注入的bean而言,這是有用的。不過這并不意味這被排除的bean自己就 不能使用自動裝配來注入其他bean。更確切得說,該bean本身不會被考慮作為其他bean自動裝配的候選者。
#### 1.4.6. 方法注入
在大部分的應用場景中,容器中的大部分bean是singletons類型的。當一個單例bean需要和另外一個單例bean, 協作時,或者一個費單例bean要引用另外一個非單例bean時,通常情況下將一個bean定義為另外一個bean的屬性值就行了。不過對于具有不同生命周期的bean 來說這樣做就會有問題了,比如在調用一個單例類型bean A的某個方法,需要引用另一個非單例(prototype)類型bean B,對于bean A來說,容器只會創建一次,這樣就沒法 在需要的時候每次讓容器為bean A提供一個新的bean B實例。
上面問題的一個解決方法是放棄控制反轉,你可以實現ApplicationContextAware接口來讓bean A感知到容器, 并且在需要的時候通過使用使用getBean("B")向容器請求一個(新的)bean B實例。下面的例子使用了這個方法:
~~~Java
// a class that uses a stateful Command-style class to perform some processing
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() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
~~~
上面的例子并沒有達到期望的效果,因為業務代碼和Spring框架產生的耦合。方法注入,作為Spring Ioc容器的高級特性,可以以一種 干凈的方法來處理這種情況。
你可以在 [this blog entry](https://spring.io/blog/2004/08/06/method-injection/)閱讀更多關于方法注入的動機。
##### Lookup 方法注入
Lookup方法具有使容器覆蓋受容器管理的bean方法的能力,從而返回指定名字的bean實例。在上述場景中,Lookup方法注入適用于原型bean。 Lookup方法注入的內部機制是Spring利用了CGLIB庫在運行時生成二進制代碼的功能,通過動態創建Lookup方法bean的子類從而達到復寫Lookup方法的目的。
為了使動態子類起作用,Spring容器要子類化的類不能是final,并且需要復寫的方法也不能是final。同樣的,要測試一個包含 抽象方法的類也稍微有些不同,你需要子集編寫它的子類提供該抽象方法的實現。最后,作為方法注入目標的bean不能是序列化的。 在Spring 3.2之后再也沒必要添加CGLIB到classpath,因為CGLIB的類打包在了org.springframework下并且在Spring核心JAR中有所描述。 這樣做既方便,又避免了與其他使用了不同版本CGLIB的項目的沖突。
再看一下在之前代碼片段中的CommandManager類,你可以發現Spring容器會自動復寫createCommand()方法的實現。CommandManager類 將不會有任何的Spring依賴,下面返工的例子可以看出:
~~~Java
package fiona.apple;
// no more Spring imports!
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),此方法的定義需要按以下形式進行:
~~~Java
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
~~~
如果方法是抽象,動態生成的子類會實現該方法。否則,動態生成的子類會覆蓋類里的具體方法。譬如:
~~~xml
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" 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="command"/>
</bean>
~~~
標識為commandManager的bean在需要一個新的command bean實例時會調用createCommand()方法。你必須將`command`bean部署為 原型(prototype)類型,如果這是實際需要的話。如果部署為singleton。那么每次將返回相同的 `command`bean。
或者,在基于注釋的組件模型中,可以通過@lookup 注釋聲明一個查找方法:
~~~Java
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 根據查找方法聲明的返回類型得到解析:
~~~Java
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
~~~
請注意,您通常會聲明這種帶有具體存根實現的注釋查找方法,以便它們與 Spring 的組件掃描規則兼容,在這里,抽象類默認會被忽略。 此限制不適用于顯式注冊或顯式導入的 bean 類的情況。
>[info]另一種訪問不同范圍的目標 bean 的方法是 ObjectFactory / Provider 注入點。 查看**Scoped beans as dependencies.**
>
>感興趣的讀者也可以找到 ServiceLocatorFactoryBean (在 org.springframework.beans.factory.config 包中)是有用的。
##### 自定義方法的替代方案
比起Lookup方法注入來,還有一種較少用到的方法注入形式,該注入能使用bean的另一個方法實現去替換自定義方法的方法。 除非你真的需要該功能,否則可以略過本節。
使用基于XML配置文件時,你可以使用replaced-method元素來達到用另一個方法來取代已有方法的目的。考慮下面的類,我們將覆蓋 computeValue方法。
~~~Java
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
~~~
實現org.springframework.beans.factory.support.MethodReplacer接口的類提供了新的方法定義。
~~~Java
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
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定義中指定了要配置的原始類和將要復寫的方法:
~~~xml
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
~~~
你可以在\<replaced-method/>元素中可以包含多個<arg-type/>元素,這些元素用來標明被復寫的方法簽名。只有被復寫的方法 存在重載的情況和同名的多個方法變體。為了方便,參數的類型字符可以采用全限定類名的簡寫。例如,下面的字符串都標識參數類型 為java.lang.String:
~~~
java.lang.String
String
Str
~~~
因為參數的數目通常足夠用來區別每個可能的選擇,這個結晶能減少很多鍵盤輸入的工作,它允許你只輸入最短的匹配參數類型的字符串。