接上篇:Spring 核心技術(3)
version 5.1.8.RELEASE
1.4.2 依賴關系及配置詳情
如上一節所述,你可以將 bean 屬性和構造函數參數定義為對其他托管 bean(協作者)的引用,或者作為內聯定義的值。Spring 基於 XML 的配置元數據為此目的支持子元素<property/>
和<constructor-arg/>
。
直接值(基本類型,字符串等)
<property/>
元素的 value
屬性指定一個屬性或構造器參數為可讀的字符串。Spring 的轉換服務用於將這些值從 String
轉換為屬性或參數的實際類型。以下示例展示了要設置的各種值:
<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
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 定義時使用支持自動屬性完成的 IDE(例如 IntelliJ IDEA 或 Spring Tool Suite),否則會在運行時而不是設計時發現拼寫錯誤。強烈建議使用此類 IDE 幫助。
還可以配置 java.util.Properties
實例,如下所示:
<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/>
元素。以下示例展示了如何使用它:
<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>
第一種形式優於第二種形式,因為使用 idref
標簽可以讓容器在部署時驗證引用的命名 bean 是否存在。在第二個變體中,不會對傳遞給 client
bean targetName
屬性的值執行驗證。只有在 client
bean 真正實例化時才會發現拼寫錯誤(很可能是致命的結果)。如果 client
bean 是原型 bean,那么可能在部署容器后很長時間才能發現此錯誤和產生的異常。
4.0 beans XSD 不再支持
idref
元素的local
屬性,因為它不再提供除常規bean
引用以外的值。升級到 4.0 版本時,需要將現有idref local
引用更改idref bean
。
其中一個共同的地方(至少早於 Spring 2.0 版本),<idref/>
元素的值是 ProxyFactoryBean
bean 定義中 AOP 攔截器的配置項。指定攔截器名稱時使用 <idref/>
元素可防止拼寫錯誤的攔截器 ID。
引用其他 Bean (協作者)
ref
元素是 <constructor-arg/>
或 <property/>
定義元素內部的最終元素。在這里,你將 bean 指定屬性的值設置為對容器管理的另一個 bean(協作者)的引用。引用的 bean 是要設置屬性的 bean 的依賴項,並且在設置該屬性之前根據需要對其進行初始化。(如果協作者是單例 bean,它可能已經被容器初始化。)所有引用最終都是對另一個對象的引用。作用域和有效性取決於是否通過 bean
、local
或 parent
屬性指定其他對象的 ID 或名稱。
通過 <ref />
標簽的 bean
屬性指定目標 bean 是最常用的方式,允許創建對同一容器或父容器中的任何 bean 的引用,不管它是否在同一 XML 文件中。bean
屬性的值可以與目標 Bean 的 id
屬性相同,也可以與目標 bean 的 name
屬性之一相同。以下示例演示如何使用 ref
元素:
<ref bean="someBean"/>
通過 parent
屬性指定目標 bean 會創建對當前容器的父容器中的 bean 的引用。parent
屬性的值可以與目標 bean 的 id
屬性相同,也可以與目標 bean 的 name
屬性之一相同。目標 bean 必須位於當前 bean 的父容器中。當容器具備層次結構,且希望和父級 bean 一樣通過代理的方式包裝父容器中現有的 bean 時,應該主要使用 bean 引用變量。以下一對列表演示了如何使用該 parent
屬性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
<!-- 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>
4.0 beans XSD 不再支持
ref
元素的local
屬性,因為它不再提供除常規bean
引用以外的值。升級到 4.0 架構時,需要將現有ref local
引用更改ref bean
。
內部 Bean
<property/>
或 <constructor-arg/>
元素中的 <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 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 或名稱。即使指定,容器也不使用其作為標識符。容器也會在創建時忽略 scope
標志,因為內部 bean 始終是匿名的,並且始終和外部 bean 一起創建。內部 bean 無法單獨訪問或注入協作 bean 中。
作為一個極端情況,可以從自定義作用域接收銷毀回調,例如包含在單例 bean 中作用域為 request 的內部 bean。內部 bean 實例的創建與包含它的 bean 相關聯,但是銷毀回調允許它參與 request 作用域的生命周期。這不是常見的情況。內部 bean 通常只是共享包含它的 bean 的作用域。
集合
<list/>
、<set/>
、<map/>
和 <props/>
元素分別設置 Java Collection
類型 List
、Set
、Map
和 Properties
的屬性和參數。以下示例演示了如何使用它們:
<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 的鍵或值,或 set 的值,可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合並
Spring 容器支持合並集合。應用程序開發人員可以定義父級 <list/>
、<map/>
、<set/>
或 <props/>
元素,並定義子元素 <list/>
、<map/>
、<set/>
或 <props/>
繼承並覆蓋父集合的值。也就是說,子集合的元素會覆蓋父集合中指定的值,子集合的值是合並父集合和子集合的元素的結果。
關於合並的這一部分討論了父子 bean 機制。不熟悉父子 bean 定義的讀者可能希望在繼續閱讀之前閱讀之前閱讀相關部分。
以下示例演示了集合合並:
<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 定義中 adminEmails
屬性的 <props/>
元素的 merge=true
屬性的使用。當容器解析並實例化 child
bean 時,生成的實例有一個 adminEmails
Properties
集合,其中包含將子集合 adminEmails
與父 adminEmails
集合合並的結果 。以下清單顯示了結果:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子元素的 Properties
集合的值繼承了父元素 <props/>
的所有屬性元素,子元素中 support
的值將覆蓋父集合的值。
這一合並行為同樣適用於 <list/>
、<map/>
和 <set/>
集合類型。在 <list/>
元素的特定場景中,建議保持與 List
集合類型(即有序集合的概念)相關聯的語義。父級的值位於所有子級列表的值之前。在 Map
、Set
和 Properties
集合類型場景中,不存在次序的。因此,容器內部使用的 Map
、Set
以及 Properties
實現類也是無序的。
集合合並的局限性
你無法合並不同的集合類型(例如 Map
和 List
)。如果你嘗試這樣做,則會拋出對應的 Exception
。merge
屬性必須在較低層級的子定義上指定。在父集合定義上指定 merge
屬性是多余的,不會導致所需的合並。
強類型集合
通過在 Java 5 中引入的泛型類型,你可以使用強類型集合。也就是說,可以聲明一種 Collection
類型,使得它只能包含(例如)String
元素。如果使用 Spring 將強類型 Collection
依賴注入到 bean 中,可以使用 Spring 的類型轉換支持,以便強類型 Collection
實例的元素在添加到 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
類型,並將字符串值(9.99
, 2.75
和 3.99
)轉換為實際 Float
類型。
null 和空字符串
Spring 將屬性等的空參數視為空字符串。以下基於 XML 的配置元數據片段將 email
屬性設置為空字符串("")。
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的示例等效於以下 Java 代碼:
exampleBean.setEmail("");
<null/>
元素可以處理 null
值。以下顯示了一個示例:
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上述配置等同於以下 Java 代碼:
exampleBean.setEmail(null);
帶有 p 命名空間的 XML 快捷方式
p 命名空間允許你使用 bean
元素的屬性(而不是嵌套 <property/>
元素)來描述屬性值或協作 bean
。
Spring 支持具備命名空間的可擴展配置格式,這些命名空間基於 XML Schema 定義。本章中討論的 beans
配置格式在一個 XML Schema 文檔中定義。但是,p 命名空間未在 XSD 文件中定義,僅存在於 Spring 的核心中。
以下示例顯示了兩個 XML 片段(第一個使用標准 XML 格式,第二個使用 p 命名空間)解析為相同的結果:
<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="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
該示例演示了 bean 定義中調用的 p 命名空間設置 email
屬性。這告訴 Spring 包含一個屬性聲明。如之前所述,p 命名空間沒有模式定義,因此可以將屬性的名稱設置為屬性名。
下一個示例包括另外兩個 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
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"/>
創建從 bean john
到 bean jane
的引用 ,而第二個 bean 定義使用 p:spouse-ref="jane"
作為屬性來執行完全相同的操作。在這種情況下,spouse
是屬性名稱,而 -ref
部分表示這不是直接值,而是對另一個 bean 的引用。
p 命名空間不如標准 XML 格式靈活。例如,聲明屬性引用的格式與
Ref
結尾的屬性沖突,而標准 XML 格式則不會。我們建議仔細選擇你的方法並將其傳達給其他團隊成員,以避免生成同時使用三種方法的 XML 文檔。
帶有 c 命名空間的 XML 快捷方式
與帶有 p 命名空間的 XML 快捷方式類似,Spring 3.1 中引入的 c 命名空間允許使用內聯屬性來配置構造函數參數,而不是嵌套 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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
通過名稱設置構造函數參數時,c:
命名空間和 p:
使用相同的約定(尾部 -ref 的 bean 引用)。類似地,它需要在 XML 文件中聲明,即使它沒有在 XSD schema 中定義(它存在於 Spring 核心內部)。
對於構造函數參數名稱不可用的罕見情況(通常在沒有調試信息的情況下編譯字節碼),可以使用備用的參數索引,如下所示:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
因為采用 XML 語法,且 XML 屬性名稱不能以數字開頭(即使某些 IDE 允許),所以索引表示法要求存在前綴
_
。<constructor-arg>
元素也可以使用相應的索引符號,但不常用,因為聲明順序通常就足夠了。
實際上,構造函數解析機制在匹配參數時非常有效,因此除非確實需要,否則我們建議在整個配置中使用名稱表示法。
復合屬性名稱
設置 bean 屬性時,可以使用復合或嵌套屬性名稱,只要除最終屬性名稱之外的路徑的所有組件都不是 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。為了確保其可以正常運行,在構造 bean 之后,something
的 fred
屬性和 fred
屬性的 bob
屬性不得為 null。否則將會拋出 NullPointerException
。
1.4.3 運用 depends-on
如果一個 bean 是另一個 bean 的依賴項,那通常意味着將一個 bean 設置為另一個 bean 的屬性。通常情況下可以使用基於 XML 的配置元數據中的 <ref/>
元素來完成此操作。但是,有時 bean 之間的依賴關系不那么直接。例如需要在類中觸發的靜態初始化程序,例如數據庫驅動程序注冊。depends-on
屬性可以在初始化使用此元素的 bean 之前顯式的強制初始化一個或多個 bean。以下示例使用 depends-on
屬性表示對單個bean的依賴關系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示對多個 bean 的依賴關系,請提供 bean 名稱列表作為 depends-on
屬性的值(逗號,空格和分號是有效的分隔符):
<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" />
depends-on
屬性可以指定初始化時的依賴性,也可以指定單例 bean 銷毀時的依賴性。通過depends-on
定義的依賴項會在指定 bean 本身銷毀之前被銷毀。這樣,depends-on
也可以控制停止順序。
1.4.4 延遲初始化的 Bean
默認情況下,ApplicationContext
實現會在初始化時創建和配置所有單例 bean,作為初始化過程的一部分。通常,這種預先實例化是合理的,因為配置或環境中的錯誤可以立刻被發現,而不是幾小時甚至幾天后。當不希望出現這種情況時,可以通過將 bean 定義標記為延遲初始化來阻止單例 bean 的預實例化。延遲初始化的 bean 告訴 IoC 容器在第一次請求時創建 bean 實例,而不是在啟動時。
在 XML 中,此行為由 <bean/>
元素的 lazy-init
屬性控制,如以下示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
當前面的配置被 ApplicationContext
使用時,lazy
bean 不會在 ApplicationContext
啟動時預先實例化,而 not.lazy
bean 會被預先實例化。
但是當延遲初始化的 bean 是未進行延遲初始化的單例 bean 的依賴項時,ApplicationContext
會在啟動時創建延遲初始化的 bean
,因為它必須滿足單例的依賴關系。延遲初始化的 bean
會被注入到其他不是延遲初始化的單例 bean 中。
你還可以通過使用 <beans/>
元素的 default-lazy-init
上的屬性來控制容器級別的延遲初始化,如以下示例顯示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>