1. 程式人生 > >Spring 核心技術(4)

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,它可能已經被容器初始化。)所有引用最終都是對另一個物件的引用。作用域和有效性取決於是否通過 beanlocalparent 屬性指定其他物件的 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 型別 ListSetMapProperties 的屬性和引數。以下示例演示瞭如何使用它們:

<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 集合型別(即有序集合的概念)相關聯的語義。父級的值位於所有子級列表的值之前。在 MapSetProperties 集合型別場景中,不存在次序的。因此,容器內部使用的 MapSet 以及 Properties 實現類也是無序的。

集合合併的侷限性

你無法合併不同的集合型別(例如 MapList)。如果你嘗試這樣做,則會丟擲對應的 Exceptionmerge 屬性必須在較低層級的子定義上指定。在父集合定義上指定 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.753.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 之後,somethingfred 屬性和 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