Spring 5.0.0框架介紹_中文版_3.4

文章作者:Tyan
博客:noahsnail.com

3.4 依賴(lài)

????????標(biāo)準(zhǔn)企業(yè)應(yīng)用不會(huì)由一個(gè)對(duì)象(或Spring用語(yǔ)中的bean)組成。即使是最簡(jiǎn)單的應(yīng)用也是由一些對(duì)象共同工作,呈現(xiàn)給終端用戶(hù)用戶(hù)看到的是一個(gè)連貫的應(yīng)用。接下來(lái)的一節(jié)闡述了如何從定義許多獨(dú)立的bean定義到完全實(shí)現(xiàn)的應(yīng)用,它是一個(gè)通過(guò)對(duì)象協(xié)作來(lái)實(shí)現(xiàn)目標(biāo)的過(guò)程。

3.4.1 依賴(lài)注入

????????依賴(lài)注入(DI)是一個(gè)處理過(guò)程,憑借對(duì)象之間依賴(lài)關(guān)系,也就是和它們一起工作的其它對(duì)象,只能通過(guò)構(gòu)造函數(shù)參數(shù),傳遞參數(shù)給工廠(chǎng)方法,在構(gòu)造完成或工廠(chǎng)方法返回的對(duì)象實(shí)例之后再設(shè)置對(duì)象實(shí)例的屬性。當(dāng)創(chuàng)建bean時(shí)容器再將這些依賴(lài)對(duì)象注入進(jìn)去。這個(gè)過(guò)程從根本上顛倒了bean本身通過(guò)直接構(gòu)建類(lèi)或通過(guò)一種機(jī)制例如服務(wù)定位模式來(lái)控制依賴(lài)對(duì)象的實(shí)例化或定位,因此命名為控制反轉(zhuǎn)(IoC)

????????使用依賴(lài)注入原則會(huì)使代碼更簡(jiǎn)潔,當(dāng)對(duì)象由依賴(lài)關(guān)系提供時(shí)解耦更有效。對(duì)象不會(huì)查找它的依賴(lài),不知道依賴(lài)的位置和依賴(lài)關(guān)系的類(lèi)別。同樣的,你的類(lèi)也變的更容易測(cè)試,尤其是依賴(lài)關(guān)系在接口或抽象基類(lèi)之間的時(shí)候,這種情況下單元測(cè)試中會(huì)要求存樁或模擬實(shí)現(xiàn)。(注:Stub和Mock都是軟件測(cè)試中使用的東西,如有疑問(wèn)請(qǐng)自行g(shù)oogle或百度)。

????????依賴(lài)有兩個(gè)主要變種,基于構(gòu)造函數(shù)的依賴(lài)注入和基于Setter的依賴(lài)注入。

基于構(gòu)造函數(shù)的依賴(lài)注入

????????基于構(gòu)造函數(shù)的依賴(lài)注入通過(guò)容器調(diào)用有參數(shù)的構(gòu)造函數(shù)來(lái)實(shí)現(xiàn),每個(gè)參數(shù)表示一個(gè)依賴(lài)。調(diào)用指定參數(shù)的靜態(tài)工廠(chǎng)方法來(lái)構(gòu)造bean是近似等價(jià)的,這里的討論將給構(gòu)造函數(shù)和靜態(tài)工廠(chǎng)方法傳參看成是類(lèi)似的。接下來(lái)的例子展示了一個(gè)類(lèi)僅能通過(guò)構(gòu)建函數(shù)注入進(jìn)行依賴(lài)注入。注意這個(gè)類(lèi)沒(méi)什么特別的,它是一個(gè)POJO,不依賴(lài)于容器特定的接口,基類(lèi)或注解。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on a MovieFinder
    private MovieFinder movieFinder;

    // a constructor so that the Spring container can inject a MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...

}

構(gòu)造函數(shù)參數(shù)解析

????????構(gòu)造函數(shù)參數(shù)解析使用參數(shù)類(lèi)型進(jìn)行匹配。如果bean定義的構(gòu)造函數(shù)參數(shù)中不存在潛在的歧義,bean定義中定義構(gòu)造函數(shù)參數(shù)的順序?yàn)閎ean實(shí)例化時(shí)提供給恰當(dāng)構(gòu)造函數(shù)的參數(shù)順序。細(xì)想下面的類(lèi):

package x.y;

public class Foo {

    public Foo(Bar bar, Baz baz) {
        // ...
    }

}

????????不存在潛在的歧義,假設(shè)Bar類(lèi)和Baz類(lèi)之間不存在繼承關(guān)系。因此下面的配置會(huì)工作良好,你不必在<constructor-arg/>元素中顯式的指定構(gòu)造函數(shù)參數(shù)索引的與/或類(lèi)型。

<beans>
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
    </bean>

    <bean id="bar" class="x.y.Bar"/>

    <bean id="baz" class="x.y.Baz"/>
</beans>

????????當(dāng)引用另一個(gè)bean時(shí),類(lèi)型已知,匹配正確(像上面的例子一樣)。當(dāng)使用簡(jiǎn)單類(lèi)型時(shí),例如<value>true</value>,Spring不能決定值的類(lèi)型,因此沒(méi)有幫助不能按類(lèi)型匹配。考慮下面的例子:

package examples;

public class ExampleBean {

    // Number of years to calculate the Ultimate Answer
    private int years;

    // The Answer to Life, the Universe, and Everything
    private String ultimateAnswer;

    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

????????在上面的場(chǎng)景中,如果你用type屬性顯式的指定了構(gòu)造參數(shù)的類(lèi)型,對(duì)于簡(jiǎn)單類(lèi)型容器可以使用類(lèi)型匹配。例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

????????使用index屬性來(lái)顯式的指定構(gòu)造函數(shù)參數(shù)的索引,例如:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

????????除了要解析多個(gè)簡(jiǎn)單值的歧義性之外,當(dāng)構(gòu)造函數(shù)有兩個(gè)相同類(lèi)型的的參數(shù)時(shí),指定索引可以解決歧義性問(wèn)題。注意索引是從0開(kāi)始的。

????????你也可以使用構(gòu)造函數(shù)參數(shù)名字解決值的歧義問(wèn)題。

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg name="years" value="7500000"/>
    <constructor-arg name="ultimateAnswer" value="42"/>
</bean>

????????記住,要使這個(gè)起作用你的代碼必須使用調(diào)試模式進(jìn)行編譯,這樣Spring可以從構(gòu)造函數(shù)中查找參數(shù)名稱(chēng)。如果你不能用調(diào)試模式進(jìn)行編譯(或不想),你可以使用JDK注解@ConstructorProperties顯式的命名你的構(gòu)造函數(shù)參數(shù)。樣板類(lèi)如下所示:

package examples;

public class ExampleBean {

    // Fields omitted

    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }

}

基于Setter的依賴(lài)注入

????????基于Setter的依賴(lài)注入在容器調(diào)用無(wú)參構(gòu)造函數(shù)或無(wú)參靜態(tài)工廠(chǎng)方法之后,通過(guò)調(diào)用bean的setter方法來(lái)實(shí)現(xiàn)依賴(lài)注入。

????????下面的例子顯示了一個(gè)類(lèi)只能通過(guò)純粹的setter注入進(jìn)行依賴(lài)注入。這個(gè)類(lèi)是常見(jiàn)的Java類(lèi)。它是一個(gè)不依賴(lài)于容器中特定接口、基類(lèi)或注解的POJO。

public class SimpleMovieLister {

    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;

    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }

    // business logic that actually uses the injected MovieFinder is omitted...

}

????????ApplicationContext支持基于構(gòu)造函數(shù)和基于setter對(duì)它管理的bean進(jìn)行依賴(lài)注入。它也支持一些依賴(lài)通過(guò)構(gòu)造函數(shù)方法注入之后,使用基于setter的依賴(lài)注入。使用BeanDefinition形式配置依賴(lài)項(xiàng),結(jié)合PropertyEditor實(shí)例可以將屬性從一種形式轉(zhuǎn)成另一種形式。然而大多數(shù)Spring用戶(hù)直接使用這些類(lèi)(例如以編程形式),而使用XML定義bean,注解組件(例如類(lèi)中使用 @Component,,@Controller注解等等),或在基于Java的@Configuration類(lèi)使用@Bean方法。

使用基于構(gòu)造函數(shù)的依賴(lài)注入還是基于setter的依賴(lài)注入?

你可以混合使用基于構(gòu)造函數(shù)的依賴(lài)注入和基于setter的依賴(lài)注入,強(qiáng)制依賴(lài)使用構(gòu)造函數(shù)注入,可選依賴(lài)使用setter方法或配置方法注入是一個(gè)很好的經(jīng)驗(yàn)法則。注意在setter方法上使用@Required注解會(huì)檢查依賴(lài)是否注入。

當(dāng)實(shí)現(xiàn)的應(yīng)用組件是不可變對(duì)象時(shí),Spring團(tuán)隊(duì)通常主張構(gòu)造函數(shù)注入,這樣可以確保所需的依賴(lài)非空。此外,基于構(gòu)造函數(shù)注入的組件總是以完全初始化狀態(tài)返回客戶(hù)(調(diào)用)代碼。作為附注,含有許多構(gòu)造函數(shù)參數(shù)的代碼給人的感覺(jué)很差,這意味著類(lèi)可能有很多職責(zé),應(yīng)該進(jìn)行重構(gòu)以便更好的處理關(guān)注問(wèn)題的分離。

setter注入應(yīng)該主要用來(lái)可選依賴(lài)上,在類(lèi)內(nèi)可以給可選依賴(lài)指定合理的默認(rèn)值。此外,在每處使用依賴(lài)的代碼都要進(jìn)行非空檢查。setter注入的一個(gè)好處就是setter方法使類(lèi)的對(duì)象在后面可以進(jìn)行再配置或再注入。JMX MBeans的管理是setter注入一個(gè)非常好的案例。

使用依賴(lài)注入的類(lèi)型對(duì)于特定的類(lèi)是最有意義的。有時(shí)候,當(dāng)處理沒(méi)有源碼的第三方類(lèi)時(shí),使用哪種方式取決于你。例如,如果第三方庫(kù)沒(méi)有提供任何setter方法,構(gòu)造函數(shù)注入可能是依賴(lài)注入唯一可行的方式。

依賴(lài)解析過(guò)程

????????容器按下面的過(guò)程處理bean依賴(lài)解析:

  • 創(chuàng)建ApplicationContext并使用描述所有bean的配置元數(shù)據(jù)初始化ApplicationContext,配置元數(shù)據(jù)可以通過(guò)XML,Java代碼或注解指定。

  • 對(duì)于每一個(gè)bean,它的依賴(lài)通過(guò)屬性、構(gòu)造函數(shù)參數(shù)、或靜態(tài)工廠(chǎng)方法參數(shù)的形式表示,靜態(tài)工廠(chǎng)方法可以替代標(biāo)準(zhǔn)的構(gòu)造函數(shù)。當(dāng)bean在實(shí)際創(chuàng)建時(shí),這些依賴(lài)會(huì)提供給bean。

  • 每個(gè)屬性或構(gòu)造函數(shù)參數(shù)或者是根據(jù)實(shí)際定義設(shè)置的值,或者是容器中另一個(gè)bean的引用。

  • 每個(gè)屬性或構(gòu)造函數(shù)參數(shù)是一個(gè)從指定形式轉(zhuǎn)成實(shí)際類(lèi)型的屬性或構(gòu)造函數(shù)參數(shù)的值。

????????當(dāng)容器創(chuàng)建后Spring容器會(huì)驗(yàn)證每個(gè)bean的配置。然而,bean屬性本身只有bean創(chuàng)建時(shí)才會(huì)進(jìn)行設(shè)置。bean是單例的并且當(dāng)容器創(chuàng)建時(shí)會(huì)進(jìn)行提前實(shí)例化(默認(rèn)情況)。作用范圍是在3.5 小節(jié)"Bean scopes"中定義的。此外,只有需要時(shí)候才會(huì)創(chuàng)建bean。bean的創(chuàng)建可能會(huì)引起beans圖的創(chuàng)建,當(dāng)bean的依賴(lài)和它的依賴(lài)的依賴(lài)(等等)創(chuàng)建和指定的時(shí)候。注意這些依賴(lài)中解析不匹配可能會(huì)在后面出現(xiàn),例如,受影響的bean第一次創(chuàng)建時(shí)。

循環(huán)依賴(lài)

如果你主要使用構(gòu)造函數(shù)注入,有可能會(huì)出現(xiàn)一個(gè)不能解決的循環(huán)依賴(lài)狀況。

例如,類(lèi)A需要通過(guò)構(gòu)造函數(shù)注入得到一個(gè)類(lèi)B的實(shí)例,而類(lèi)B需要通過(guò)構(gòu)造函數(shù)獲得一個(gè)類(lèi)A的實(shí)例。如果你為類(lèi)A和類(lèi)B配置了互相注入的bean,Spring IoC容器在運(yùn)行時(shí)檢測(cè)到循環(huán)引用,會(huì)拋出BeanCurrentlyInCreationException。

一個(gè)可能的解決方案是編譯某個(gè)類(lèi)的源代碼使其通過(guò)setter注入而不是構(gòu)造函數(shù)注入。或者,避免構(gòu)造函數(shù)注入僅用setter注入。換句話(huà)說(shuō),盡管是不被推薦的,但你可以通過(guò)setter注入配置循環(huán)依賴(lài)。

不像通常的情況(沒(méi)有循環(huán)依賴(lài)),bean A和bean B之間的循環(huán)依賴(lài)可以強(qiáng)制其中的一個(gè)bean優(yōu)先注入另一個(gè)bean中,可以使其完全初始化(古老的雞/蛋場(chǎng)景)。

????????通常情況下你可以信任Spring去做正確的事情。在容器加載時(shí)它檢測(cè)配置問(wèn)題,例如引用不存在的beans和循環(huán)依賴(lài)。當(dāng)bean實(shí)際創(chuàng)建時(shí),Spring設(shè)置屬性和解析依賴(lài)盡可能的晚。這意味著Spring容器正確加載但后面可能會(huì)產(chǎn)生異常,當(dāng)你請(qǐng)求一個(gè)對(duì)象時(shí),創(chuàng)建對(duì)象或它的某個(gè)依賴(lài)時(shí)出現(xiàn)問(wèn)題,這時(shí)容器就會(huì)拋出異常。例如,由于缺失或存在無(wú)效屬性,bean會(huì)拋出異常。在真正需要這些beans之前創(chuàng)建它們,會(huì)花費(fèi)一些前期時(shí)間和內(nèi)存,當(dāng)ApplicationContext創(chuàng)建時(shí)你會(huì)發(fā)現(xiàn)配置問(wèn)題,而不是在創(chuàng)建之后。為了單例bean懶惰初始化而不是預(yù)先實(shí)例化,你仍需要重寫(xiě)這個(gè)默認(rèn)行為。

????????如果沒(méi)有循環(huán)依賴(lài)存在,當(dāng)一個(gè)或更多協(xié)作beans注入到一個(gè)獨(dú)立的bean中,在注入獨(dú)立bean之前,每個(gè)協(xié)作bean都是完全配置的。這意味著如果bean A有個(gè)依賴(lài)為bean B,Spring IoC容器在調(diào)用bean A的setter方法之前會(huì)完整的配置bean B。換句話(huà)說(shuō),bean被實(shí)例化(不是預(yù)先實(shí)例化的單例),設(shè)置依賴(lài)和相關(guān)的生命周期方法(例如配置初始化方法或初始化bean回調(diào)方法)被調(diào)用。

依賴(lài)注入的例子

????????下面的例子使用基于XML的配置元數(shù)據(jù)進(jìn)行setter注入。Spring XML配置文件中的一小部分指定了一些bean的定義:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- setter injection using the nested ref element -->
    <property name="beanOne">
        <ref bean="anotherExampleBean"/>
    </property>

    <!-- setter injection using the neater ref attribute -->
    <property name="beanTwo" ref="yetAnotherBean"/>
    <property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    public void setBeanOne(AnotherBean beanOne) {
        this.beanOne = beanOne;
    }

    public void setBeanTwo(YetAnotherBean beanTwo) {
        this.beanTwo = beanTwo;
    }

    public void setIntegerProperty(int i) {
        this.i = i;
    }

}

????????在上面的例子中,setter聲明匹配XML文件中指定的屬性。下面的例子使用了基于構(gòu)造函數(shù)的依賴(lài)注入:

<bean id="exampleBean" class="examples.ExampleBean">
    <!-- constructor injection using the nested ref element -->
    <constructor-arg>
        <ref bean="anotherExampleBean"/>
    </constructor-arg>

    <!-- constructor injection using the neater ref attribute -->
    <constructor-arg ref="yetAnotherBean"/>

    <constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    public ExampleBean(
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }

}

????????bean定義中指定的構(gòu)造函數(shù)參數(shù)將作為ExampleBean的構(gòu)造函數(shù)參數(shù)使用。

????????現(xiàn)在考慮這個(gè)例子的一個(gè)變種,不使用構(gòu)造函數(shù),而是Spring調(diào)用靜態(tài)工廠(chǎng)方法返回對(duì)象的一個(gè)實(shí)例:

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
    <constructor-arg ref="anotherExampleBean"/>
    <constructor-arg ref="yetAnotherBean"/>
    <constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {

    // a private constructor
    private ExampleBean(...) {
        ...
    }

    // a static factory method; the arguments to this method can be
    // considered the dependencies of the bean that is returned,
    // regardless of how those arguments are actually used.
    public static ExampleBean createInstance (
        AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

        ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }

}

????????靜態(tài)工廠(chǎng)方法的參數(shù)通過(guò)<constructor-arg/>元素提供,與構(gòu)造函數(shù)使用的完全一樣。雖然這個(gè)例子中工廠(chǎng)方法返回值的類(lèi)型與包含靜態(tài)工廠(chǎng)方法的類(lèi)的類(lèi)型一樣,但它們可以不一樣。工廠(chǎng)方法的實(shí)例(非靜態(tài))的使用本質(zhì)上樣式完全一樣(除了使用factory-bean屬性代替class屬性之外),因此這兒不討論這些細(xì)節(jié)。

3.4.2 依賴(lài)和配置的細(xì)節(jié)

????????正如上一節(jié)提到的那樣,你可以通過(guò)引用其它被管理bean(協(xié)作者)來(lái)定義bean的屬性和構(gòu)造函數(shù)參數(shù),或者在行內(nèi)定義值。為了實(shí)現(xiàn)這個(gè)功能,Spring的基于XML的配置元數(shù)據(jù)在它的<property/><constructor-arg/>中支持子元素類(lèi)型。

直接使用值 (基本類(lèi)型,字符串等等)

????????<property/>元素的value屬性指定了一個(gè)屬性或構(gòu)造函數(shù)參數(shù)作為可讀的字符串表示。使用Spring的轉(zhuǎn)換服務(wù)將這些值從String轉(zhuǎn)成屬性或參數(shù)的真實(shí)類(lèi)型。

<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>

????????下面的例子為了更簡(jiǎn)潔的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 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是更簡(jiǎn)潔的;然而,錯(cuò)別字是在運(yùn)行時(shí)發(fā)現(xiàn)而不是在設(shè)計(jì)時(shí),除非你使用IDE例如IntelliJ IDEASpring Tool Suite (STS),當(dāng)你創(chuàng)建bean定義時(shí)它們支持自動(dòng)的屬性補(bǔ)全。IDE輔助是強(qiáng)烈推薦的。

????????你也可以配置java.util.Properties實(shí)例:

<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容器通過(guò)JavaBeans的PropertyEditor機(jī)制將<value/>元素內(nèi)部的文本轉(zhuǎn)成java.util.Properties實(shí)例。這是一個(gè)很好的捷徑,使用嵌入的<value/>元素而不是使用value屬性的方式,是Spring團(tuán)隊(duì)支持的幾個(gè)地方之一。

idref元素

????????在容器中傳遞另一個(gè)bean的id(字符串值,不是引用)到<constructor-arg/><property/>元素時(shí),idref元素是一種簡(jiǎn)單的的誤差檢驗(yàn)方式。

<bean id="theTargetBean" class="..."/>

<bean id="theClientBean" class="...">
    <property name="targetName">
        <idref bean="theTargetBean" />
    </property>
</bean>

????????上面的bean定義片段與下面的代碼片段是等價(jià)的(運(yùn)行時(shí)):

<bean id="theTargetBean" class="..." />

<bean id="client" class="...">
    <property name="targetName" value="theTargetBean" />
</bean>

????????第一種形式優(yōu)于第二種形式,因?yàn)?code>idref標(biāo)簽允許容器在部署時(shí)驗(yàn)證引用的bean,即命名的bean實(shí)際存在。在第二種形式中,當(dāng)值傳給clienttargetName時(shí)沒(méi)有進(jìn)行驗(yàn)證。拼寫(xiě)錯(cuò)誤只有在clientbean實(shí)際創(chuàng)建時(shí)才會(huì)發(fā)現(xiàn)(最可能有嚴(yán)重后果)。如果client bean是原型bean,拼寫(xiě)錯(cuò)誤和產(chǎn)生的異常可能只有在容器部署很長(zhǎng)時(shí)間之后才會(huì)發(fā)現(xiàn)。

idref元素的local屬性在4.0 beans xsd中不再支持,因?yàn)樗辉贋楹细竦腷ean引用提供值。簡(jiǎn)單將你現(xiàn)有的idref local引用改成idref bean當(dāng)更新到4.0 schema時(shí)。

????????<idref/>元素帶來(lái)值的通常位置(至少在Spring 2.0之前)是在ProxyFactoryBean bean定義中的AOP攔截器配置中。當(dāng)你指定攔截器名字時(shí)使用<idref/>元素來(lái)防止誤拼攔截器id。

其它bean的應(yīng)用(協(xié)作bean)

????????ref元素是<constructor-arg/><property/>定義元素的最終的元素。在這個(gè)元素中設(shè)置bean的指定屬性的值,值為容器管理的另一個(gè)bean(協(xié)作bean)的引用。引用的bean是設(shè)置屬性bean的依賴(lài),在屬性設(shè)置之前引用bean需要進(jìn)行初始化。(如果協(xié)作bean是一個(gè)單例模式的bean,它可能已經(jīng)被容器初始化了。)所有引用bean根本上都是另一個(gè)對(duì)象的引用。作用域和驗(yàn)證是根據(jù)你是否通過(guò)bean,local,或parent屬性指定了另一個(gè)對(duì)象的id/name來(lái)決定的。

????????通過(guò)<ref/>標(biāo)簽的bean屬性指定目標(biāo)bean是最常用的形式,允許創(chuàng)建同容器或父容器中任何bean的引用,不管它是否是在同一個(gè)XML文件中。bean屬性的值可能與目標(biāo)bean的id屬性值相同,或與目標(biāo)bean的name屬性值相同。

<ref bean="someBean"/>

????????通過(guò)parent屬性指定目標(biāo)bean會(huì)引用當(dāng)前容器的父容器中的bean。parent屬性的值可能與目標(biāo)bean的id值或name值相同,目標(biāo)bean必須在當(dāng)前容器的父容器中。當(dāng)你有一個(gè)容器分層的時(shí)候你可以使用parent,你想將現(xiàn)有bean包裹在有代理的父容器中且現(xiàn)有bean與父容器中的bean同名,你可以使用parent屬性。

<!-- in the parent context -->
<bean id="accountService" class="com.foo.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>

idref元素的local屬性在4.0 beans xsd中不再支持,因?yàn)樗辉贋楹细竦腷ean引用提供值。簡(jiǎn)單將你現(xiàn)有的idref local引用改成idref bean當(dāng)更新到4.0 schema時(shí)。

內(nèi)部bean

????????<property/><constructor-arg/>元素內(nèi)的<bean/>元素中定義bean稱(chēng)為內(nèi)部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>

????????內(nèi)部bean定義不要求定義id或name;如果指定了,容器不用用這個(gè)值作為標(biāo)識(shí)符。容器創(chuàng)建時(shí)也忽略scope標(biāo)記:內(nèi)部bean總是匿名的且它們總是由外部bean創(chuàng)建。除了注入到封閉bean中或獨(dú)立的訪(fǎng)問(wèn)它們,不可能將內(nèi)部bean注入到協(xié)作bean中。

????????作為一種很少出現(xiàn)的情況,從特定的域中有可能會(huì)收到銷(xiāo)毀回調(diào)函數(shù),例如,對(duì)于請(qǐng)求域內(nèi)的內(nèi)部bean包含單例bean:內(nèi)部bean實(shí)例的創(chuàng)建會(huì)綁定到它的包含bean,但銷(xiāo)毀回調(diào)函數(shù)允許它進(jìn)入到請(qǐng)求域的生命周期中。這不是一個(gè)常見(jiàn)的場(chǎng)景;內(nèi)部bean通常簡(jiǎn)單的共享它們的包含bean的作用域。

集合

????????在<list/>,<set/>,<map/><props/>元素中,你要分別設(shè)置Java Collection類(lèi)型list,set,mapProperties的屬性和參數(shù)。

<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的key或value,或者是set value的值也可以是下面元素中的任何一個(gè):

bean | ref | idref | list | set | map | props | value | null

集合合并

????????Spring也支持集合的合并。應(yīng)用開(kāi)發(fā)者可以定義父類(lèi)型<list/>,<map/>,<set/><props/>元素,可以有繼承和覆蓋父集合的子類(lèi)型元素<list/>,<map/><set/><props/>。也就是說(shuō),子集合的值是父集合和子集合中元素合并的結(jié)果,子集合元素覆蓋了父集合元素的值。

????????關(guān)于合并的這節(jié)討論了父子bean機(jī)制。對(duì)父子bean定義不熟悉的讀者可以去讀相關(guān)的章節(jié)。

????????下面的例子示范了集合合并:

<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屬性。當(dāng)容器解析并實(shí)例化child bean時(shí),最終的實(shí)例含有adminEmails Properties集合,集合中的值是子adminEmails集合和父adminEmails集合合并的結(jié)果。

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

????????子Properties集合的值繼承了父<props/>中的所有屬性元素,子集合中的support值覆蓋了父集合中的值。

????????<list/><map/><set/>集合類(lèi)型中的合并與上面類(lèi)似。在特定的<list/>元素情況下,關(guān)于List集合類(lèi)型的語(yǔ)義,也就是說(shuō),有序集合值的概念仍然是保留的;父list中的值領(lǐng)先于所有子list中的值。在Map,SetProperties集合類(lèi)型,不存在順序。因此,無(wú)序語(yǔ)義在容器內(nèi)部使用的集合類(lèi)型MapSetProperties的實(shí)現(xiàn)基礎(chǔ)上是有效的。

集合合并的限制

????????你不能合并不同的集合類(lèi)型(例如MapList),如果你試圖合并不同的集合類(lèi)型會(huì)有適當(dāng)?shù)膾伋?code>Exception。merge屬性必須在更低的、繼承的子定義中;在父集合定義中指定merge屬性是多余的并且不會(huì)進(jìn)行合并。

強(qiáng)類(lèi)型集合

????????隨著Java 5中泛型的引入,你可以使用強(qiáng)類(lèi)型集合。也就是說(shuō),你可以聲明一個(gè)Collection類(lèi)型但它只能包含String元素(例子)。如果你使用Spring將一個(gè)強(qiáng)類(lèi)型的Collection注入到bean中,你可以利用Spring的類(lèi)型轉(zhuǎn)換支持,例如在將元素添加到Collection之前,將你的強(qiáng)類(lèi)型Collection實(shí)例中的元素轉(zhuǎn)成恰當(dāng)?shù)念?lèi)型。

public class Foo {

    private Map<String, Float> accounts;

    public void setAccounts(Map<String, Float> accounts) {
        this.accounts = accounts;
    }
}
<beans>
    <bean id="foo" class="x.y.Foo">
        <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>

????????當(dāng)注入foo bean的accounts屬性時(shí),強(qiáng)類(lèi)型Map<String, Float>中元素類(lèi)型的泛型信息可以通過(guò)反射得到。因此Spring的類(lèi)型轉(zhuǎn)換結(jié)構(gòu)能識(shí)別各種值元素的類(lèi)型為Float,字符串9.99, 2.753.99會(huì)被轉(zhuǎn)換成實(shí)際的Float類(lèi)型。

Null和空字符串

????????Spring把屬性的空參數(shù)都處理為空Strings。下面基于XML的配置元數(shù)據(jù)片段將email屬性設(shè)為空String值("")。

<bean class="ExampleBean">
    <property name="email" value=""/>
</bean>

????????上面的例子與下面的Java代碼是等價(jià)的:

exampleBean.setEmail("")

????????<null/>元素處理null值。例如:

<bean class="ExampleBean">
    <property name="email">
        <null/>
    </property>
</bean>

????????上面的配置與下面的Java代碼等價(jià)。

exampleBean.setEmail(null)

XML 使用p命名空間的縮寫(xiě)

????????p命名空間可以讓你不需要嵌入<property/>元素便能使用bean元素的屬性來(lái)描述你的屬性值以及/或協(xié)作beans。

????????Spring支持含有命名空間的擴(kuò)展配置形式,命名控件是基于XML Schema定義的。本章討論的beans配置形式是在XML Schema文檔中定義的。但是p命名空間不能在XSD文件中定義并且只在Spring core中存在。

????????下面的例子顯示了兩個(gè)XML片段,解析結(jié)果是相同的:第一個(gè)是標(biāo)準(zhǔn)的XML形式,第二個(gè)使用了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="foo@bar.com"/>
    </bean>

    <bean name="p-namespace" class="com.example.ExampleBean"
        p:email="foo@bar.com"/>
</beans>

????????這個(gè)例子顯示了bean定義中p命名空間中有個(gè)一個(gè)叫email的屬性。這會(huì)通知Spring包含屬性聲明。如前面所述,p命名空間沒(méi)有schema定義,因此你可以將特性值(attribute)設(shè)到屬性值(property)上。

????????下面的例子包括兩個(gè)bean定義,且它們都引用了另一個(gè)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>

????????正如你所看到的,這個(gè)例子不僅包括使用了p命名空間的屬性值,而且使用了一種特定的形式來(lái)聲明屬性引用。然而第一個(gè)bean定義使用<property name="spouse" ref="jane"/>創(chuàng)建了一個(gè)從bean john到bean jane的引用,第二個(gè)bean定義使用p:spouse-ref="jane"作為一個(gè)特性同樣定義了從bean john到bean jane的引用。在spouse是屬性名的情況下,-ref部分表示這不是一個(gè)直接的值而是另一個(gè)bean的引用。

p命名空間不是標(biāo)準(zhǔn)的XML格式,例如,聲明的屬性引用會(huì)與以Ref結(jié)尾的屬性相沖突,而標(biāo)準(zhǔn)XML格式則不會(huì)。我們建議你仔細(xì)的選擇你的方法并與你的團(tuán)隊(duì)成員交流,避免生成的XML文檔同時(shí)使用了三種方式。

XML 使用c命名空間的縮寫(xiě)

????????與“XML shortcut with the p-namespace”小節(jié)類(lèi)似,在Spring 3.1新引入的c命名空間允許使用行內(nèi)屬性配置構(gòu)造函數(shù)參數(shù)而不用嵌入constructor-arg元素。

????????讓我們重新回顧一下“Constructor-based dependency injection”小節(jié)中的例子并使用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="bar" class="x.y.Bar"/>
    <bean id="baz" class="x.y.Baz"/>

    <!-- traditional declaration -->
    <bean id="foo" class="x.y.Foo">
        <constructor-arg ref="bar"/>
        <constructor-arg ref="baz"/>
        <constructor-arg value="foo@bar.com"/>
    </bean>

    <!-- c-namespace declaration -->
    <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/>

</beans>

????????c:命名空間遵循與p:命名空間相同的約定在通過(guò)名字設(shè)置構(gòu)造函數(shù)參數(shù)時(shí)。同樣的,它也需要進(jìn)行聲明,雖然它不能在XSD schema中使用(但在Spring core中存在)。

????????對(duì)于很少出現(xiàn)的不能找到構(gòu)造函數(shù)參數(shù)名字的情況(通常如果編譯字節(jié)碼且沒(méi)有調(diào)試信息),可以使用參數(shù)索引:

<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>

由于XML語(yǔ)法,索引符號(hào)需要前面加上_,因?yàn)閄ML屬性名字不能以數(shù)字開(kāi)頭(即使一些IDE允許)。

????????在實(shí)踐中,構(gòu)造函數(shù)解析機(jī)制能有效匹配參數(shù),因此除非真的需要,否則我們推薦在配置中使用名字符號(hào)。

混合屬性名字

????????當(dāng)你設(shè)置bean屬性時(shí),你可以使用混合的或嵌入的屬性名字,只要路徑中除了最后的屬性名之外所有組件都是非null。考慮下面的bean定義。

<bean id="foo" class="foo.Bar">
    <property name="fred.bob.sammy" value="123" />
</bean>

????????foobean有一個(gè)fred屬性,fred有一個(gè)sammy屬性,bob有一個(gè)sammy屬性,最后的sammy屬性設(shè)置值為123。為了這樣設(shè)置,foofred屬性,fredbob屬性在bean創(chuàng)建后必須是非null或拋出NullPointerException。

3.4.3 使用depends-on

????????如果一個(gè)bean是另一個(gè)bean的一個(gè)依賴(lài),這通常意味著一個(gè)bean作為另一個(gè)bean的一個(gè)屬性去設(shè)置。在基于XML的配置元數(shù)據(jù)中通常使用<ref/>元素實(shí)現(xiàn)。然而有時(shí)beans之間的依賴(lài)關(guān)系是間接的;例如,類(lèi)中的靜態(tài)初始化程序需要觸發(fā),例如數(shù)據(jù)驅(qū)動(dòng)注冊(cè)。depends-on特性能顯示的強(qiáng)制一個(gè)bean或多個(gè)beans在使用這個(gè)元素的bean初始化之前進(jìn)行初始化。下面的例子使用depends-on特性表示一個(gè)單一bean的一個(gè)依賴(lài):

<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />

????????為了表示多個(gè)bean上的依賴(lài)關(guān)系,提供一個(gè)bean名字列表作為depends-on特性的值,用逗號(hào),空格或分號(hào)作為有效分隔符:

<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定義中可以指定初始化時(shí)的依賴(lài)和對(duì)應(yīng)的銷(xiāo)毀時(shí)依賴(lài)(僅在單例情況下)。依賴(lài)beans與給定bean之間定義了一個(gè)depends-on關(guān)系,依賴(lài)beans在給定bean本身被銷(xiāo)毀之前首先被銷(xiāo)毀。因此depends-on也可以控制銷(xiāo)毀順序。

3.4.4 延遲初始化beans

????????默認(rèn)情況下,作為初始化過(guò)程的一部分,ApplicationContext實(shí)現(xiàn)時(shí)渴望創(chuàng)建并配置所有的單例beans。通常情況下,預(yù)實(shí)例化是必要的,因?yàn)榕渲弥谢蛑車(chē)h(huán)境中的錯(cuò)誤可以立即發(fā)現(xiàn),與幾小時(shí)或幾天后發(fā)現(xiàn)截然相反。當(dāng)預(yù)實(shí)例化是不必要的時(shí)候,你可通過(guò)標(biāo)記bean定義為延遲初始化來(lái)阻止單例bean的預(yù)實(shí)例化。延遲初始化的bean會(huì)通知IoC容器當(dāng)?shù)谝淮握?qǐng)求bean時(shí)創(chuàng)建一個(gè)bean實(shí)例,而不是在啟動(dòng)時(shí)創(chuàng)建。

????????在XML中,延遲初始化通過(guò)<bean/>元素中的lazy-init特性來(lái)控制;例如:

<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>

????????當(dāng)ApplicationContext讀取到上面的配置,ApplicationContext啟動(dòng)時(shí)名字為lazy的bean不會(huì)進(jìn)行預(yù)實(shí)例化,而名字為not.lazy的bean會(huì)進(jìn)行預(yù)實(shí)例化。

????????然而,當(dāng)延遲初始化的bean是一個(gè)非延遲初始化的單例bean的依賴(lài)時(shí),ApplicationContext會(huì)在啟動(dòng)時(shí)創(chuàng)建延遲初始化的bean,因?yàn)樗仨毺峁﹩卫齜ean的依賴(lài)。延遲初始化的bean會(huì)注入到單例bean中,而在其它地方它是非延遲初始化的。

????????你也可以在容器中通過(guò)<beans/>中的default-lazy-init特性控制延遲初始化;例如:

<beans default-lazy-init="true">
    <!-- no beans will be pre-instantiated... -->
</beans>

3.4.5 自動(dòng)注入?yún)f(xié)作bean

????????Spring容器能自動(dòng)裝配協(xié)作beans之間的關(guān)聯(lián)關(guān)系。你可以允許Spring通過(guò)檢查ApplicationContext中的內(nèi)容自動(dòng)的為你的bean解析協(xié)作者(其它bean)。自動(dòng)裝配有以下優(yōu)勢(shì):

  • 自動(dòng)裝配能明顯減少指定屬性或構(gòu)造函數(shù)參數(shù)的需要。(其它的機(jī)制例如在本章其它地方討論的bean模板在這一點(diǎn)上也是非常重要的。)

  • 當(dāng)對(duì)象變化時(shí)自動(dòng)裝配能更新配置。例如,如果你需要增加一個(gè)類(lèi)的依賴(lài)項(xiàng),依賴(lài)項(xiàng)可以是滿(mǎn)足自動(dòng)裝配的而不需要你去修改配置。因此自動(dòng)裝配在開(kāi)發(fā)時(shí)尤其有用,當(dāng)代碼基礎(chǔ)變的更穩(wěn)定時(shí)可以改為顯式裝配。

????????當(dāng)使用基于XML的配置元數(shù)據(jù)時(shí),通過(guò)使用<bean/>元素的autowire特性你可以指定一個(gè)bean定義的自動(dòng)裝配模式。自動(dòng)注入功能有四種模式。你可以指定每個(gè)bean的自動(dòng)裝配模式,因此你可以選擇使用哪一種模式。

表 3.2 自動(dòng)裝配模式

模式 解析
no (默認(rèn))無(wú)自動(dòng)裝配。引用bean必須通過(guò)ref元素定義。對(duì)于更大的部署,不推薦更改默認(rèn)設(shè)置,因?yàn)轱@式指定協(xié)作者更清晰并且更易控制。在某種程度上來(lái)說(shuō),它記錄了系統(tǒng)的結(jié)構(gòu)。
byName 通過(guò)屬性名稱(chēng)自動(dòng)裝配。Spring尋找與需要自動(dòng)裝配的屬性同名的bean。例如,如果一個(gè)bean定義設(shè)置為通過(guò)名稱(chēng)自動(dòng)裝配,它有一個(gè)master屬性(也就是說(shuō),它有一個(gè)setMaster(..)方法),Spring尋找名字為master的bean定義,使用它設(shè)置屬性值。
byType 如果容器中含有屬性類(lèi)型已知的一個(gè)bean,那么可以允許按類(lèi)型自動(dòng)裝配屬性。如果此類(lèi)型的bean不止一個(gè),則會(huì)拋出致命的異常,這意味著你可能不能使用byType來(lái)注入那個(gè)bean。如果沒(méi)有匹配的bean,則什么也不做;屬性沒(méi)有被設(shè)置。
constructor byType類(lèi)似,但是應(yīng)用到構(gòu)造函數(shù)參數(shù)上的。如果容器中沒(méi)有一個(gè)構(gòu)造函數(shù)參數(shù)bean的確定類(lèi)型,將會(huì)拋出一個(gè)致命的異常。

????????通過(guò)byType或構(gòu)造函數(shù)自動(dòng)裝配模式,你可以配置數(shù)組和集合類(lèi)型。在這種情況下容器內(nèi)所有能匹配期望類(lèi)型的自動(dòng)裝配候選對(duì)象將被提供合適的依賴(lài)項(xiàng)。如果期望的key類(lèi)型是String類(lèi)型,你可以自動(dòng)裝配強(qiáng)類(lèi)型的Maps。自動(dòng)裝配的Maps的值將有所有匹配期望類(lèi)型的bean組成,Maps的鍵將包含對(duì)應(yīng)的bean名稱(chēng)。

????????你可以將依賴(lài)檢查與自動(dòng)裝配相結(jié)合,它將在自動(dòng)裝配完成之后執(zhí)行。

自動(dòng)裝配的優(yōu)勢(shì)與限制

????????當(dāng)自動(dòng)裝配在整個(gè)工程中一致的使用時(shí)其效果最好。如果通常情況下不使用自動(dòng)裝配,僅在一兩個(gè)bean定義中使用自動(dòng)裝配開(kāi)發(fā)人員可能感到非常困惑。

????????考慮一下自動(dòng)裝配的限制與缺點(diǎn):

  • propertyconstructor-arg中顯式依賴(lài)的設(shè)置總是會(huì)覆蓋自動(dòng)裝配。你不能自動(dòng)裝配所謂的簡(jiǎn)單屬性例如基本類(lèi)型,StringsClasses(和簡(jiǎn)單類(lèi)型的數(shù)組)。這是設(shè)計(jì)上的限制。

  • 與顯式配置相比,自動(dòng)裝配是更不確定的。盡管Spring小心的避免猜測(cè)以防歧義性引起無(wú)法預(yù)料的后果,但Spring管理的對(duì)象之間的關(guān)系不再被顯式的記錄。

  • Spring容器中能產(chǎn)生文檔的工具可能得不到配置信息。

  • setter方法或構(gòu)造函數(shù)參數(shù)指定的類(lèi)型進(jìn)行自動(dòng)裝配時(shí)可能匹配到容器中多個(gè)bean的定義。對(duì)于數(shù)組,集合或Maps而言,這是一個(gè)不必要的問(wèn)題。然而對(duì)于只期望一個(gè)值的依賴(lài)而言,這個(gè)歧義性不能任意解決。如果不能獲得唯一的bean定義,會(huì)拋出異常。

????????后面的方案中,你有一些選擇:

  • 放棄自動(dòng)裝配支持顯式配置。

  • 通過(guò)設(shè)置bean的autowire-candidate特性為false來(lái)避免自動(dòng)裝配。

  • 通過(guò)設(shè)置<bean/>元素的primary特性為true來(lái)指定一個(gè)單例bean定義作為主要的候選bean。

  • 通過(guò)基于注解的配置實(shí)現(xiàn)更多細(xì)顆粒的控制,如3.9小節(jié) "基于注解的容器配置"。

排除bean在自動(dòng)裝配之外

????????在單個(gè)bean的基礎(chǔ)上,你可以排除bean在自動(dòng)裝配之外。在Spring的XML形式中,設(shè)置<bean/>元素的autowire-candidate特性為false;容器會(huì)使自動(dòng)裝配基礎(chǔ)框架不能得到指定bean定義(包括注解類(lèi)型的配置,例如@Autowired)。

????????你也可以根據(jù)bean名稱(chēng)的匹配模式限制自動(dòng)裝配的候選目標(biāo)。頂層的<beans/>元素可以接收default-autowire-candidates特性中的一個(gè)或多個(gè)模式。例如,為了限制自動(dòng)裝配候選目標(biāo)匹配任何名字以Repository結(jié)尾的bean,可以提供一個(gè)*Repository值。為了提供多種模式,可以定義一個(gè)以逗號(hào)為分隔符的列表。bean定義中autowire-candidate特性顯示的值truefalse最是優(yōu)先起作用的,對(duì)于這些bean而言,模式匹配規(guī)則不起作用。

????????這些技術(shù)對(duì)于那些你從不想通過(guò)自動(dòng)裝配方式注入到其它bean中的beans而言是很有用的。這不意味著一個(gè)排除的bean它本身不能通過(guò)自動(dòng)裝配進(jìn)行配置。更確切的說(shuō),bean本身不是一個(gè)進(jìn)行其它bean進(jìn)行自動(dòng)裝配的候選者。

3.4.6 方法注入

????????在大多數(shù)應(yīng)用場(chǎng)景中,容器中的大多數(shù)bean是單例的。當(dāng)一個(gè)單例bean需要與另一個(gè)單例bean協(xié)作時(shí),或一個(gè)非單例bean需要與另一個(gè)非單例bean協(xié)作時(shí),你通常通過(guò)定義一個(gè)bean作為另一個(gè)bean的一個(gè)屬性來(lái)處理這個(gè)依賴(lài)關(guān)系。當(dāng)bean的生命周期不同時(shí)問(wèn)題就出現(xiàn)了。假設(shè)一個(gè)單例bean A需要使用非單例(標(biāo)準(zhǔn))bean B時(shí),也許A中的每一個(gè)方法調(diào)用都要使用bean B。容器僅創(chuàng)建單例bean A一次,因此僅有一次設(shè)置屬性的機(jī)會(huì)。容器不能在每次需要bean B時(shí)提供一個(gè)bean B的新的實(shí)例。

????????一個(gè)解決方案是放棄一些控制反轉(zhuǎn)。你可以使bean A通過(guò)實(shí)現(xiàn)ApplicationContextAware接口感知到容器,每個(gè)bean A需要的時(shí)候就通過(guò)getBean("B")調(diào)用向容器請(qǐng)求(通常是新的)一個(gè)bean B的實(shí)例。下面是這種方法的一個(gè)例子:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(
            ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

????????前面所講的不是讓人滿(mǎn)意的,因?yàn)闃I(yè)務(wù)代碼能感知并耦合了Spring框架。方法注入,Spring IoC容器的一個(gè)有點(diǎn)高級(jí)的特性,允許使用一種干凈的方式來(lái)處理這個(gè)案例。

你可以在blog entry中了解更多關(guān)于方法注入的動(dòng)機(jī)。

查找方法注入

????????查找方法注入是容器的一種覆蓋其管理的beans中的方法的能力,可以返回容器中另一個(gè)命名bean查找結(jié)果。查找通常會(huì)涉及到一個(gè)標(biāo)準(zhǔn)bean,如前一小節(jié)中講的那樣。Spring框架實(shí)現(xiàn)了查找方法注入,它是通過(guò)使用CGLIB庫(kù)生成的字節(jié)碼來(lái)動(dòng)態(tài)的產(chǎn)生一個(gè)覆蓋這個(gè)方法的子類(lèi)。

  • 為了使動(dòng)態(tài)子類(lèi)化起作用,Spring bean容器要進(jìn)行子類(lèi)化的類(lèi)不能是最終的類(lèi),要進(jìn)行重寫(xiě)的方法也不是最終的方法。
  • 單元測(cè)試一個(gè)含有抽象方法的類(lèi)需要你自己對(duì)這個(gè)類(lèi)進(jìn)行子類(lèi)化,并且提供這個(gè)抽象方法的stub實(shí)現(xiàn)。
  • 實(shí)體方法對(duì)于要求獲得實(shí)體類(lèi)的組件掃描也是必需的。
  • 一個(gè)更關(guān)鍵的限制是查找方法不能與工廠(chǎng)方法一起工作,尤其是在配置類(lèi)中不能與@Bean方法同時(shí)起作用,由于那種情況下容器不能控制實(shí)例的創(chuàng)建,因此不能在飛速寫(xiě)入中創(chuàng)建一個(gè)運(yùn)行時(shí)產(chǎn)生的子類(lèi)。
  • 最后,方法注入的目標(biāo)對(duì)象不能被序列化。

????????看一下前面代碼片中的CommandManager類(lèi),你可以看到Spring容器將會(huì)動(dòng)態(tài)的覆蓋createCommand()方法的實(shí)現(xiàn)。CommandManager類(lèi)不會(huì)有任何Spring依賴(lài),重寫(xiě)的例子如下:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}

????????客戶(hù)類(lèi)中包含要注入的方法(在這個(gè)例子中是CommandManager),要注入的方法需要下面形式的一個(gè)簽名:

<public|protected> [abstract] <return-type> theMethodName(no-arguments);

????????如果這個(gè)方法是抽象的,動(dòng)態(tài)產(chǎn)生的子類(lèi)會(huì)實(shí)現(xiàn)這個(gè)方法。另外,動(dòng)態(tài)產(chǎn)生的子類(lèi)會(huì)覆蓋原來(lái)的類(lèi)中定義的實(shí)體方法。例如:

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="command"/>
</bean>

????????無(wú)論什么時(shí)候識(shí)別為commandManager的bean需要一個(gè)command bean的新實(shí)例,它都會(huì)調(diào)用它的createCommand()方法。如果真的需要的話(huà),你必須小心的部署command bean為一個(gè)原型。如果它被部署為一個(gè)單例,每次都會(huì)返回同一個(gè)command實(shí)例。

感興趣的讀者可能也會(huì)發(fā)現(xiàn)ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)使用這種方法。ServiceLocatorFactoryBean中使用的方法與另一個(gè)工具類(lèi)ObjectFactoryCreatingFactoryBean中的方法類(lèi)似,但它允許你指定你自己的查找接口,與Spring特定的查找接口相反。這些類(lèi)的額外信息請(qǐng)查詢(xún)Java文檔。

任意的方法替換

????????一種比查找方法注入更少使用的形式是用另一種方法實(shí)現(xiàn)替換管理的bean中任意方法的能力。用戶(hù)可以安全跳過(guò)本節(jié)剩下的部分,直到這個(gè)方法真正需要的時(shí)候再看。

????????在基于XML的配置元數(shù)據(jù)中,對(duì)于一個(gè)部署的bean,你可以通過(guò)replaced-method元素用另一個(gè)方法實(shí)現(xiàn)替換現(xiàn)有的方法實(shí)現(xiàn)??紤]下面的類(lèi),有一個(gè)我們想覆蓋的computeValue方法:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...

}

????????實(shí)現(xiàn)了org.springframework.beans.factory.support.MethodReplacer接口的類(lèi)提供了一種新的方法定義。

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

????????部署最初的類(lèi)的bean定義和指定的重寫(xiě)方法如下:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

????????你可以在<replaced-method/>元素中使用一個(gè)或多個(gè)包含<arg-type/>元素來(lái)指出要覆蓋的方法的方法簽名。只有類(lèi)中進(jìn)行了方法重載且有多個(gè)重載變種的時(shí)候,參數(shù)的簽名才是必需的。為了簡(jiǎn)便,字符串類(lèi)型的參數(shù)可能是全拼類(lèi)型名稱(chēng)的一個(gè)子串。例如,下面的所有寫(xiě)法都能匹配java.lang.String

java.lang.String
String
Str

????????因?yàn)閰?shù)數(shù)目經(jīng)常是足夠區(qū)分每個(gè)可能的選擇的,通過(guò)允許定義匹配參數(shù)類(lèi)型的最短字符串類(lèi)型,這個(gè)縮寫(xiě)可以保存許多類(lèi)型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容