相依性與組態詳解
如前一節所述,您可以將 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://127.0.0.1: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://127.0.0.1:3306/mydb"
p:username="root"
p:password="misterkaoli"/>
</beans>
先前的 XML 更簡潔。但是,除非您使用 IDE (例如 IntelliJ IDEA 或 Spring 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://127.0.0.1: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 是否實際存在。在第二種變體中,不會對傳遞給 client
Bean 的 targetName
屬性的值執行驗證。只有在實際實例化 client
Bean 時,才會發現拼字錯誤 (很可能導致致命結果)。如果 client
Bean 是 prototype Bean,則此拼字錯誤與產生的例外狀況可能僅在容器部署很久之後才會發現。
4.0 Bean XSD 中不再支援 idref 元素上的 local 屬性,因為它不再提供優於常規 bean 參考的值。升級到 4.0 Schema 時,請將現有的 idref local 參考變更為 idref bean 。 |
至少在 Spring 2.0 之前的版本中,<idref/>
元素帶來價值的一個常見位置是在 ProxyFactoryBean
Bean 定義中組態 AOP 攔截器。當您指定攔截器名稱時,使用 <idref/>
元素可防止您拼錯攔截器 ID。
對其他 Bean (協作者) 的參考
ref
元素是 <constructor-arg/>
或 <property/>
定義元素內的最後一個元素。在這裡,您將 Bean 的指定屬性的值設定為對容器管理的另一個 Bean (協作者) 的參考。被參考的 Bean 是要設定其屬性的 Bean 的相依性,並且會在設定屬性之前根據需要按需初始化。(如果協作者是 Singleton 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 同名的 Proxy 包裝父容器中的現有 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 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 Bean XSD 中不再支援 ref 元素上的 local 屬性,因為它不再提供優於常規 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。
作為一個邊緣案例,可以從自訂作用域接收銷毀回呼,例如,對於包含在 Singleton 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>
Map 鍵或值,或 Set 值,也可以是以下任何元素
bean | ref | idref | list | set | map | props | value | null
集合合併
Spring 容器也支援合併集合。應用程式開發人員可以定義父 <list/>
、<map/>
、<set/>
或 <props/>
元素,並讓子 <list/>
、<map/>
、<set/>
或 <props/>
元素繼承與覆寫父集合中的值。也就是說,子集合的值是合併父集合與子集合元素後的結果,其中子集合的元素會覆寫父集合中指定的值。
本節關於合併的部分討論了父子 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
屬性的 <props/>
元素上使用 merge=true
屬性。當容器解析與實例化 child
Bean 時,產生的實例具有 adminEmails
Properties
集合,其中包含合併子集合的 adminEmails
集合與父集合的 adminEmails
集合的結果。以下清單顯示結果
子 Properties
集合的值設定繼承父 <props/>
中的所有屬性元素,並且子集合的 support
值會覆寫父集合中的值。
此合併行為類似地應用於 <list/>
、<map/>
和 <set/>
集合類型。在 <list/>
元素的特定情況下,與 List
集合類型相關聯的語意 (即 ordered
值集合的概念) 會被維護。父集合的值位於所有子列表的值之前。在 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-namespace 的 XML 捷徑
p-namespace 可讓您使用 bean
元素的屬性 (而不是巢狀 <property/>
元素) 來描述您的屬性值協作 Bean,或兩者兼具。
Spring 支援可延伸的組態格式,具有 namespace,這些格式基於 XML Schema 定義。本章討論的 beans
組態格式定義在 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 定義中稱為 email
的 p-namespace 中的屬性。這告訴 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 命名空間的屬性值,還使用特殊格式來宣告屬性參考。第一個 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
) ,依名稱設定建構子引數。 同樣地,即使它未在 XSD 結構描述中定義 (它存在於 Spring 核心中),也需要在 XML 檔案中宣告它。
對於建構子引數名稱不可用的罕見情況 (通常是如果 bytecode 編譯時沒有 -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
屬性,而 bob
屬性又具有 sammy
屬性,最終的 sammy
屬性被設定為值 123
。 為了使此操作生效,在 bean 建構之後,something
的 fred
屬性和 fred
的 bob
屬性都不能為 null
。 否則,將拋出 NullPointerException
。