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 IDEASpring 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">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</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">administrator@example.com</prop>
                <prop key="support">support@example.com</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">sales@example.com</prop>
                <prop key="support">support@example.co.uk</prop>
            </props>
        </property>
    </bean>
<beans>

注意 child bean 定義中 adminEmails 屬性的 <props/> 元素的 merge=true 屬性的使用。當容器解析並實例化 child bean 時,生成的實例有一個 adminEmails Properties 集合,其中包含將子集合 adminEmails 與父 adminEmails 集合合並的結果 。以下清單顯示了結果:

administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk

子元素的 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="someone@somewhere.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="someone@somewhere.com"/>
</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="something@somewhere.com"/>
    </bean>

    <!-- c-namespace declaration with argument names -->
    <bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
        c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>

</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="something@somewhere.com"/>

因為采用 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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM