Spring 核心技術(4)
接上篇: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">[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 定義的讀者可能希望在繼續閱讀之前閱讀之前閱讀相關部分。
以下示例演示了集合合併:
<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
集合合併的結果 。以下清單顯示了結果:
[email protected]
[email protected]
[email protected]
子元素的 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="[email protected]"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="[email protected]"/>
</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="[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:
使用相同的約定(尾部 -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="[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
屬性,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>
- 我的CSDN:https://blog.csdn.net/liweitao7610
- 我的部落格園:https://www.cnblogs.com/aotian/
- 我的簡書:https://www.jianshu.com/u/6b6e162f1fdc