依賴項和配置詳情

如前一節所述,你可以將 Bean 屬性和建構函式引數定義為對其他託管 Bean(協作物件)的引用,或者定義為內聯的值。Spring 基於 XML 的配置元資料在其 <property/><constructor-arg/> 元素中支援子元素型別來實現此目的。

直值(Primitive 型別、String 等)

<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://:3306/mydb"/>
	<property name="username" value="root"/>
	<property name="password" value="misterkaoli"/>
</bean>

以下示例使用p-namespace進行更簡潔的 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 id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
		destroy-method="close"
		p:driverClassName="com.mysql.jdbc.Driver"
		p:url="jdbc:mysql://:3306/mydb"
		p:username="root"
		p:password="misterkaoli"/>

</beans>

上述 XML 更簡潔。然而,錯誤拼寫是在執行時而非設計時發現的,除非你使用支援自動屬性完成(property completion)的 IDE(例如 IntelliJ IDEASpring Tools for Eclipse),當你建立 Bean 定義時。強烈推薦此類 IDE 輔助。

你也可以配置一個 java.util.Properties 例項,如下所示:

<bean id="mappings"
	class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">

	<!-- typed as a java.util.Properties -->
	<property name="properties">
		<value>
			jdbc.driver.className=com.mysql.jdbc.Driver
			jdbc.url=jdbc:mysql://: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="theClientBean" class="...">
	<property name="targetName" ref="theTargetBean"/>
</bean>

第一種形式優於第二種形式,因為使用 idref 標籤允許容器在部署時驗證引用的命名 Bean 是否實際存在。在第二種變體中,對傳遞給客戶端 Bean 的 targetName 屬性的值不執行任何驗證。只有當客戶端 Bean 實際例項化時,才能發現拼寫錯誤(很可能導致致命結果)。如果客戶端 Bean 是一個原型 Bean,這個拼寫錯誤和由此產生的異常可能只有在容器部署很久之後才能發現。

idref 元素上的 local 屬性在 4.0 Bean XSD 中不再支援,因為它不再提供高於常規 Bean 引用的價值。升級到 4.0 schema 時,請將現有的 idref local 引用更改為 idref bean

在配置 AOP 攔截器時,ProxyFactoryBean Bean 定義中的 <idref/> 元素是一個帶來價值的常用位置(至少在 Spring 2.0 之前的版本中)。在使用 <idref/> 元素指定攔截器名稱時,可以防止誤拼攔截器 ID。

引用其他 Bean(協作物件)

ref 元素是 <constructor-arg/><property/> 定義元素內的最後一個元素。在此處,你將 Bean 的指定屬性的值設定為對容器管理的另一個 Bean(協作物件)的引用。被引用的 Bean 是其屬性要設定的 Bean 的一個依賴項,它在屬性設定之前根據需要按需初始化。(如果協作物件是單例 Bean,它可能已經被容器初始化)。所有引用最終都是對另一個物件的引用。作用域和驗證取決於你透過 beanparent 屬性指定其他物件的 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 引用變體。以下兩段示例展示瞭如何使用 parent 屬性:

<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
	<!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context, 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>
ref 元素上的 local 屬性在 4.0 Bean XSD 中不再支援,因為它不再提供高於常規 Bean 引用的價值。升級到 4.0 schema 時,請將現有的 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 中。

作為一種特殊情況,可以從自定義作用域接收銷燬回撥 — 例如,對於包含在單例 Bean 中的請求作用域內部 Bean。內部 Bean 例項的建立與其包含 Bean 繫結在一起,但銷燬回撥允許它參與請求作用域的生命週期。這不是一個常見場景。內部 Bean 通常只是簡單地共享其包含 Bean 的作用域。

集合

<list/><set/><map/><props/> 元素分別設定 Java 集合型別 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">[email protected]</prop>
			<prop key="support">[email protected]</prop>
			<prop key="development">[email protected]</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>

對映的鍵或值,或者集合的值,也可以是以下任何元素:

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">[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>

請注意,在子 Bean 定義的 adminEmails 屬性的 <props/> 元素上使用了 merge=true 屬性。當容器解析並例項化子 Bean 時,生成的例項將擁有一個 adminEmails Properties 集合,其中包含子 adminEmails 集合與父 adminEmails 集合合併的結果。以下列表顯示了結果:

Properties 集合的值集繼承了父 <props/> 中的所有屬性元素,並且子集合中 support 屬性的值覆蓋了父集合中的值。

這種合併行為類似地適用於 <list/><map/><set/> 集合型別。在 <list/> 元素的特定情況下,保留了與 List 集合型別相關的語義(即值的有序集合概念)。父集合的值位於所有子列表的值之前。在 Map、Set 和 Properties 集合型別的情況下,不存在排序。因此,對於容器內部使用的 Map、Set 和 Properties 實現型別所代表的集合型別,沒有排序語義生效。

集合合併的限制

你不能合併不同的集合型別(例如 Map 和 List)。如果嘗試這樣做,會丟擲相應的 Exceptionmerge 屬性必須在較低層級的、繼承的子定義中指定。在父集合定義中指定 merge 屬性是多餘的,並且不會產生期望的合併效果。

強型別集合

藉助 Java 對泛型型別的支援,你可以使用強型別集合。也就是說,可以宣告一個 Collection 型別,使其只能包含(例如)String 元素。如果你使用 Spring 將強型別 Collection 透過依賴注入到 Bean 中,則可以利用 Spring 的型別轉換支援,將強型別 Collection 例項中的元素在新增到 Collection 之前轉換為相應的型別。以下 Java 類和 Bean 定義展示瞭如何實現這一點:

  • Java

  • Kotlin

public class SomeClass {

	private Map<String, Float> accounts;

	public void setAccounts(Map<String, Float> accounts) {
		this.accounts = accounts;
	}
}
class SomeClass {
	lateinit var accounts: Map<String, Float>
}
<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 程式碼:

  • Java

  • Kotlin

exampleBean.setEmail("");
exampleBean.email = ""

<null/> 元素處理 null 值。以下列表顯示了一個示例:

<bean class="ExampleBean">
	<property name="email">
		<null/>
	</property>
</bean>

前述配置等價於以下 Java 程式碼:

  • Java

  • Kotlin

exampleBean.setEmail(null);
exampleBean.email = null

使用 p-namespace 的 XML 快捷方式

p-namespace 允許你使用 Bean 元素的屬性(而不是巢狀的 <property/> 元素)來描述你的屬性值、協作 Bean,或者兩者兼有。

Spring 支援帶有名稱空間的可擴充套件配置格式,這些格式基於 XML Schema 定義。本章討論的 Bean 配置格式在 XML Schema 文件中定義。然而,p-namespace 沒有在 XSD 檔案中定義,僅存在於 Spring 核心中。

以下示例顯示了兩個 XML 片段(第一個使用標準 XML 格式,第二個使用 p-namespace),它們解析為相同的結果:

<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>

示例顯示了 Bean 定義中 p-namespace 中的一個名為 email 的屬性。這告訴 Spring 包含一個屬性宣告。如前所述,p-namespace 沒有 schema 定義,因此你可以將屬性的名稱設定為屬性名。

下一個示例包含另外兩個 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 定義使用 <property name="spouse" ref="jane"/> 建立從 Bean john 到 Bean jane 的引用,而第二個 Bean 定義使用屬性 p:spouse-ref="jane" 執行完全相同的操作。在這種情況下,spouse 是屬性名,而 -ref 部分表示這不是一個直值,而是對另一個 Bean 的引用。

p-namespace 不如標準 XML 格式靈活。例如,宣告屬性引用的格式與以 Ref 結尾的屬性會發生衝突,而標準 XML 格式則不會。我們建議你仔細選擇你的方法,並將其告知你的團隊成員,以避免同時使用所有這三種方法生成 XML 文件。

使用 c-namespace 的 XML 快捷方式

使用 p-namespace 的 XML 快捷方式類似,c-namespace 於 Spring 3.1 引入,允許使用內聯屬性來配置建構函式引數,而不是使用巢狀的 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"/>

	<!-- 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="[email protected]"/>
	</bean>

	<!-- c-namespace declaration with argument names -->
	<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 中定義(它存在於 Spring 核心內部),也需要在 XML 檔案中宣告它。

在建構函式引數名稱不可用的極少數情況下(通常是位元組碼編譯時沒有使用 -parameters 標誌),你可以回退到引數索引,如下所示:

<!-- 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 允許)。相應的索引表示法也可用於 <constructor-arg> 元素,但不常用,因為在那裡簡單的宣告順序通常就足夠了。

在實踐中,建構函式解析機制在匹配引數方面非常高效,因此除非確實需要,我們建議在整個配置中使用名稱表示法。

複合屬性名

在設定 bean 屬性時,可以使用複合或巢狀屬性名,只要路徑中除最終屬性名之外的所有元件都不是 null。考慮以下 bean 定義

<bean id="something" class="things.ThingOne">
	<property name="fred.bob.sammy" value="123" />
</bean>

something bean 有一個 fred 屬性,該屬性又有一個 bob 屬性,該屬性再有一個 sammy 屬性,而最終的 sammy 屬性被設定為值 123。為了使這能夠正常工作,somethingfred 屬性以及 fredbob 屬性在 bean 構建後不能是 null。否則,會丟擲 NullPointerException