依賴項和配置詳情
如上一節所述,您可以將 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://:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="misterkaoli"/>
</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">
<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 更簡潔。但是,除非您使用支援建立 bean 定義時自動完成屬性的 IDE(例如 IntelliJ IDEA 或 Spring Tools for Eclipse),否則拼寫錯誤會在執行時而非設計時被發現。強烈建議使用此類 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="collaborator" class="..." />
<bean id="client" class="...">
<property name="targetName">
<idref bean="collaborator" />
</property>
</bean>
上述 bean 定義片段與以下片段完全等效(在執行時)
<bean id="collaborator" class="..." />
<bean id="client" class="...">
<property name="targetName" value="collaborator" />
</bean>
第一種形式優於第二種形式,因為使用 idref 標籤允許容器在部署時驗證引用的命名 bean 確實存在。在第二種變體中,不會對傳遞給 client bean 的 targetName 屬性的值執行驗證。因此,拼寫錯誤只有在 client bean 實際例項化時才會被發現(很可能導致致命結果)。如果 client bean 是一個原型 bean,那麼這種拼寫錯誤和由此產生的異常可能在容器部署很久之後才會被發現。
至少在 Spring 2.0 之前的版本中,<idref/> 元素髮揮作用的一個常見地方是在 ProxyFactoryBean bean 定義中配置 AOP 攔截器。在指定攔截器名稱時使用 <idref/> 元素可以防止您拼錯攔截器 ID。 |
對其他 Bean 的引用(協作者)
ref 元素是 <constructor-arg/> 或 <property/> 定義元素中的最後一個元素。在這裡,您將 bean 的指定屬性的值設定為對容器管理的另一個 bean(協作者)的引用。引用的 bean 是要設定其屬性的 bean 的依賴項,它會在設定屬性之前根據需要按需初始化。(如果協作者是一個單例 bean,它可能已經被容器初始化。)所有引用最終都是對另一個物件的引用。作用域和驗證取決於您是透過 bean 還是 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 引用變體。以下兩組列表顯示瞭如何使用 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 beans XSD 中不再受支援,因為它不再比常規 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 中。
作為一種特殊情況,可以從自定義作用域接收銷燬回撥——例如,對於包含在單例 bean 中的請求作用域內部 bean。內部 bean 例項的建立與其包含 bean 繫結,但銷燬回撥允許它參與請求作用域的生命週期。這不是一個常見的情況。內部 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">[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>
請注意在 child bean 定義的 adminEmails 屬性的 <props/> 元素上使用 merge=true 屬性。當容器解析並例項化 child bean 時,生成的例項將具有一個 adminEmails Properties 集合,其中包含子 adminEmails 集合與父 adminEmails 集合合併的結果。以下列表顯示了結果
子 Properties 集合的值集繼承了父 <props/> 中的所有屬性元素,並且子集合中 support 值的子值覆蓋了父集合中的值。
此合併行為類似地適用於 <list/>、<map/> 和 <set/> 集合型別。在 <list/> 元素的特定情況下,與 List 集合型別關聯的語義(即有序值集合的概念)得以保留。父級的值位於所有子列表值之前。在 Map、Set 和 Properties 集合型別的情況下,不存在排序。因此,對於容器內部使用的關聯 Map、Set 和 Properties 實現型別所基於的集合型別,沒有排序語義生效。
集合合併的限制
您不能合併不同的集合型別(例如 Map 和 List)。如果您嘗試這樣做,將丟擲適當的 Exception。merge 屬性必須在較低的、繼承的子定義上指定。在父集合定義上指定 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 將屬性等的空引數視為空 Strings。以下基於 XML 的配置元資料片段將 email 屬性設定為空 String 值 ("")。
<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-名稱空間的 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
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 定義中一個名為 email 的 p-名稱空間屬性。這告訴 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
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-名稱空間定義了屬性值,還使用特殊格式聲明瞭屬性引用。第一個 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
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)透過名稱設定建構函式引數。同樣,它需要在 XML 檔案中宣告,即使它沒有在 XSD 模式中定義(它存在於 Spring 核心中)。
在極少數情況下,如果建構函式引數名稱不可用(通常是如果位元組碼在沒有 -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。為了使其正常工作,something 的 fred 屬性和 fred 的 bob 屬性在 bean 構造後不得為 null。否則,將丟擲 NullPointerException。