IoC容器4——依賴

依賴

典型的企業級應用不可能僅僅由一個對象組成。即便是最簡單的應用程序也擁有一些對象,它們相互協做展現給最終用戶一個耦合的應用程序。下面將介紹如何從定義一些獨立的bean到在一個完整應用中相互協做完成一個目標的一組對象。java

1 依賴注入

依賴注入是一個過程,在此過程當中對象經過構造函數參數、工廠方法參數或在對象構造或從工廠方法返回後設置設置來定義它們的依賴關係。而後容器在建立bean的時候注入這些依賴關係。整個過程是反向的,bean自身經過類的直接構造函數或Service Locater模式控制實例化和定位本身的依賴關係,所以也被稱爲控制反轉。mysql

使用DI原理,代碼會更清晰,當對象具備依賴關係時能夠更有效的解耦。對象不查找其依賴關係,而且不知道依賴關係的位置或類。所以,類會變得容易測試,特別是當依賴於接口或抽象基類時,容許在單元測試用使用村歌或模擬實現。spring

依賴注入主要使用兩種方式,一種是基於構造函數的依賴注入,另外一種的基於Setter方法的依賴注入。sql

基於構造函數的依賴注入

基於構造函數的依賴注入是經過容器調用帶參數的構造函數完成的,每一個參數表明一個依賴關係。調用指定參數的靜態工廠方法構造bean幾乎等價。下面的例子展現了一個只能經過構造函數依賴注入的例子。注意,這個類自己並無什麼特別的,它僅僅是一個POJO,並無依賴容器指定的接口、基類或者註解。數據庫

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

}

構造函數參數的解析

使用參數的類型進行構造函數參數解析。若是在bean定義時構造函數不存在潛在的歧義,那麼構造函數參數在bean定義中定義的順序就是在bean實例化時將這些參數提供給適當的構造函數的順序。參考下面的代碼:apache

package x.y;

public class Foo {

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

}

假設Bar和Baz類沒有繼承關係,就不存在潛在的模糊性。所以下面的配置能夠正常工做,不須要在<contructor-arg/>元素中指定構造函數參數的索引和類型。數組

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

當引用其它的bean,類型是已知的,匹配會正常工做(參考上面的例子)。當要使用一個簡單類型,例如<value>true</value>,Spring沒法肯定value的類型,因此沒法正常匹配。考慮下面的例子:架構

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

}

在上面的場景中,若是使用type屬性置頂了構造函數參數的類型,容器就能夠匹配簡單類型。例如:app

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

使用index屬性指定構造函數參數的順序,例如:框架

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

除了解決多個簡單值的歧義以外,指定索引能夠解析構造函數具備兩個相同類型參數的歧義。請注意,索引從0開始。

也可使用構造函數的參數名來消除value的歧義:

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

請記住,爲了使參數名消除歧義能夠開箱即用,代碼必須在啓用debug標誌餓狀況下編譯,以便Spring能夠從構造函數中查找參數名。若是沒法使用調試標誌編譯代碼(或者不想),則可使用@ConstructorProperties JDK註解來明確的命名構造函數參數。簡單的例子以下所示:

package examples;

public class ExampleBean {

    // Fields omitted

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

}

基於Setter方法的依賴注入

基於Setter方法的依賴注入是由調用bean的setter方法完成的,這個過程發生在調用無參的構造方法或無參的靜態工廠方法實例化bean以後。

下面的例子展現了一個能夠僅僅使用純Setter進行依賴注入的類。這個類是傳統的Java類,它僅僅是一個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支持使用基於構造函數的依賴注入和基於Setter方法的依賴注入來管理它的bean。它也支持在構造函數注入一部分之後關係後使用基於setter方法的依賴注入。開發者能夠在BeanDefinition中使用PropertyEditor實例來自由選擇注入的方式。可是大多數Spring用戶不須要直接使用這些class,而是使用XML格式的bean定義、註解組件(使用@Controller、@Component等註解註釋類)或者@Configuration註釋的類中的@Bean註釋的方法(Java的配置形式)。這些源代碼會在內部被轉換爲BeanDefinition的實例並用於加載整個Spring IoC容器的實例。

基於構造函數的依賴注入仍是基於Setter方法的依賴注入?

因爲能夠混合使用兩種依賴注入的方法,因此一個好的原則是對那些必需的依賴關係首選構造函數、可選的依賴關係使用setter方法或配置方法。在setter方法上使用@Required註解使得屬性變爲必須的依賴關係。

Spring團隊提倡使用基於構造函數的依賴注入,由於它能夠將應用程序的組件實現爲不可變的對象,而且保證必需的依賴關係不爲空。此外,構造函數注入的組件老是會以徹底初始化的狀態放回給客戶端(調用)代碼。附註,大量的構造函數參數是一個壞的代碼風格,這意味着該類可能有太多的責任,應該重構將不一樣職能分離。

Setter依賴注入應主要用於可選的依賴關係,便可以在類中指定合理的默認值。不然必須在使用依賴關係的每處代碼作非空檢查。setter依賴注入的好處是setter方法使得該類的對象能夠在之後從新配置或從新注入。所以,經過JMX管理MBean是setter依賴注入的一個用例。

應當使用對特定類最用意義的依賴注入形式。當處理沒有源碼的第三方類時,由您本身選擇。例如,若是第三方類沒有暴露任何setter方法,那麼構造函數注入多是惟一可用的依賴注入形式。

依賴的解析過程

容器的依賴的解析過程以下:

  • 使用描述全部bean的配置元數據建立和初始化ApplicationContext。配置元數據能夠經過XML文件、Java代碼和註解指定。
  • 每個Bean的依賴經過屬性、構造函數參數或者靜態工廠方法的參數等來表示。這些依賴會在Bean建立的的時候注入和裝載。
  • 每個屬性或者構造函數的參數都是實際定義的值或者引用容器中其餘的Bean。
  • 一個屬性或者構造參數若是是一個實際定義的值,則能夠由特定的類型轉換成屬性或構造函數的參數須要的類型。默認的,Spring將String類型轉成默認的Java內在的類型,好比int,long,String,boolean等。

建立容器時,Spring容器會驗證每一個bean的配置。可是,在bean實際建立以前不會設置bean屬性。scope屬性爲singleton並設置爲pre-instantiated(默認)的Bean將在建立容器時被建立。其它狀況下,只有在須要時才建立該bean。建立bean可能會致使建立一組bean,由於建立和分配了bean的依賴關係及其依賴關係的依賴關係(等等)。請注意,依賴關係沒法匹配的問題可能會較晚出現,即在首次建立受影響的bean之時。

循環依賴

若是主要使用基於構造函數的依賴注入,那麼頗有可能出現一個沒法解決的循環依賴的場景。好比類A經過構造函數注入一個類B的實例,而類B經過構造函數注入一個類A的實例。若是配置類A和類B的bean致使相互注入,Spring IoC容器會在運行時發現這種循環引用,並拋出BeanCurrentlyInCreationException異常。一個可行的解決辦法是在一些類的源碼中把構造函數注入改成setter注入。或者僅僅使用setter注入替代構造函數注入。儘管不被推薦,可使用setter注入方法解決循環依賴。

通常能夠信任Spring會作正確的事情。它會在容器加載時發現配置問題,例如引用的bean不存在或者循環引用。Spring會盡量晚的設置屬性和引用,當bean實際被建立時。這意味着容器加載成功會也會產生異常,當被請求的bean因爲自身或者它的引用建立出現問題時。例如,bean由於缺乏屬性或者類型錯誤時拋出異常。這可能會致使一些配置問題被延遲發現,這就時爲何ApplicationContext的實現默認使用預加載、單例模式的bean。以實際須要以前建立這些bean的一些預付時間和內存爲代價,能夠在建立ApplicationContext時發現配置問題,而不是之後。依舊能夠覆蓋默認的行爲,將singleton從預加載設置爲懶加載。

若是不存在循環依賴,當一個或多個協做bean被注入到bean中時,每一個協做bean在被注入bean以前被徹底配置。舉例來講,若是Bean A依賴於Bean B,那麼Spring IoC容器會先配置Bean B,而後調用Bean A的Setter方法。換言之,Bean先會實例化,而後注入依賴,而後纔是相關的生命週期方法(例如 configured init method 或者 InitializingBean callback method))的調用。

依賴注入的例子

下面是使用XML配置的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文件中的屬性相一致,下面的例子是基於構造函數的依賴注入:

<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定義中指定的構造函數參數將會被用於ExampleBean的構造函數的參數。

下面的例子使用靜態工廠方法返回類的實例:

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

}

使用<constructor-arg/>元素指定靜態工廠方法的參數,與使用構造函數注入相同。返回實例的類型和包含工廠方法的類不須要相同,儘管上例中是相同的。實例(非靜態)工廠方法以基本相同的方式被使用(除了使用factory-bean屬性替代class屬性)。

2 依賴和配置細節

如上文所述,能夠將bean屬性和構造函數參數設置爲其它被管理的bean的引用或者行間定義的值。Spring XML 格式的配置元數據使用<property/> <constructor-arg/>子元素完成上述配置。

直接值(基本數據類型、字符串等)

<property/>的value屬性以可讀的字符串形式定義一個屬性或者構造函數參數。Spring的conversion service將這些值從字符串轉換爲屬性或構造函數參數須要的正確類型。

<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更加簡潔,可是輸入錯誤只能在運行時發現而不是設計階段,除非使用了IntelliJ IDEA 或者 Spring Tool Suite(STS)這類在建立bean定義時支持自動補全的IDE。強烈推薦這種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容器使用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 id="theTargetBean" class="..." />

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

第一種形式優於第二種形式,由於使用idref標籤可使容器在部署時減產被引用的命名bean確實存在。使用第二種方法,不會檢查賦給targetName屬性的值。拼寫錯誤(多是致命的)只有bean實際被實例化是才能被發現。若是是一個prototypebean,拼寫錯誤和由此致使的異常可能會在容器部署好久之後纔會出現。

idref 的local屬性在4.0的beans xsd再也不被支持,由於再也不提供一個 ref bean 的引用值。若是要升級到4.0 schema 使用idref bean替代idref local便可。

一個常見的使用場景是(至少在Spring 2.0以前的版本中),在ProxyFactoryBean的定義中使用<idref/>元素配置AOP攔截器。指定攔截器的名稱時使用<idref/>元素能夠防止拼寫錯誤。

引用其它bean

<ref/>元素做爲<constructor-arg/>元素和<property/>的子元素,不能再被嵌套。它用來將容器管理的另外一個bean的引用賦值給bean的特定屬性。引用的bean是其屬性將被設置的bean的依賴關係,而且在設置屬性以前根據須要初始化它。(若是被引用的bean是singleton,它也許已經被容器實例化了。)全部的引用都指向其它對象。範圍和驗證依賴因而否經過bean, local或者parent屬性指定了另外一個對象的id或name.

經過<ref/>元素的bean屬性指定目標bean是最多見的方式,它容許建立一個引用指向在同一容器或父容器的任何一個bean,無論是否在同一XML文件中配置。bean屬性的值能夠是目標bean的id屬性或者name屬性值之一。

<ref bean="someBean"/>

經過parent屬性建立一個bean的引用,僅能夠指向父容器中的bean。屬性的值能夠是目標bean的id屬性或者name屬性值之一,而且目標bean必須是如今容器的父容器。通常只有在存在層次化容器中,但願經過代理來包裹父容器中的Bean,並在子容器使用相同名稱時纔會用到這個屬性。

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

在4.0的beans xsd中 ref元素的local屬性再也不支持,由於不提供regular bean的引用值。升級到4.0schema,把ref local改成ref bean便可。

inner bean

在<property/>或<constructor-arg/>元素中的<bean/>元素定義 inner 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>

inner bean 的定義不須要定義id或者name;若是指定,容器也不使用其做爲標識。容器在建立inner bean時一樣會忽略scope標識:inner bean 老是匿名的而且它們老是與outer bean一塊兒建立。將inner bean注入到除了包裹它的bean以外的協做bean中,或者獨立訪問它們是不可能的。能夠從自定義範圍接收銷燬回調;例如對於包含在singleton bean中的request-scoped inner bean:建立inner bean的實例會將其與包含它的bean綁定,可是銷燬回掉函數容許它共享request 範圍的生命週期。這不是一個常見的場景;inner 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">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,或者是集合的value均可以配置爲下列之中的一些元素:

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

集合合併

Spring 容器也支持集合合併。能夠定義父<list/><set/><map/>和<props/>元素,並使用子元素繼承和覆蓋父集合中的值。即子集合的值是父集合和子集合元素合併(子集合元素覆蓋父集合元素中指定的值)後的結果。

下面的例子展現了集合合併:

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

注意,子bean定義中的adminEmails中的<props/>元素使用了merge=true屬性。當子bean由容器解析並實例化時,生成的實例具備一個adminEmails屬性集合,該集合包含將該子bean的adminEmails集合與父級的adminEmails集合合併的結果。

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

子Properties集合的值繼承了全部父<props/>的值,而且子元素support的值覆蓋了父集合中的值。

這種合併行爲與<list/><map/>和<set/>集合類型類似。在<list/>元素狀況下,維護與List集合類型相關聯的語義,即有序的值集合的概念:父項的值先於全部子列表的值。而對於Map、Set和Properties集合類型,是沒有順序的。所以,對於在容器內部使用Map,Set和Properties的集合類型的實現,是沒有順序語義的。

集合合併的限制

不能合併不一樣類型的集合(例如合併Map和List),若是這麼作了會拋出一個相關異常。merge屬性必需在低級的、繼承的子定義中使用;在父集合定義中指定merge屬性是多餘的,不會產生指望的合併。

強類型集合

經過Java 5 中引入的範型,可使用強類型集合。便可以定義一個只包含String元素的集合類型。若是使用Spring依賴性注入強類型的集合到一個bean中,那麼能夠利用Spring的類型轉換支持,以便將強類型的Collection實例的元素轉換爲適當的類型,而後添加到集合。

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>

當準備註入foo 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>

上面的例子等價於下面的代碼:

exampleBean.setEmail("")

<null/>元素被解析爲null。例如:

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

上面的例子等價於下面的代碼:

exampleBean.setEmail(null)

使用 p 命名空間精簡XML

p 命名空間使你可以使用bean元素的屬性代替嵌入的<property/>元素描述屬性值和/或協做bean。

Spring支持具備命名空間的可擴展配置格式,它們基於XML模式定義。上面討論的beans配置格式在XML Schema文檔中定義。可是,p命名空間沒有在XSD文件中定義,只存在於Spring的核心。

下面例子中的兩個代碼段會被解析爲相同的結果:第一個使用標準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="foo@bar.com"/>
    </bean>

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

該示例顯示了在bean定義中名爲email的p命名空間中的屬性。它告訴Spring包含一個屬性生命。如前所述,p命名空間沒有模式定義,所以能夠將XML元素的屬性名設置爲類中的屬性名稱。

下面的例子包含了兩個引用其它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命名空間類似,在Spring 3.1 被引入的c命名空間容許使用行內屬性配置構造函數參數替代constructor-arg元素。

<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有相同的約定(以-ref的屬性用於bean引用),c命名空間用於經過名稱來設置構造函數參數。一樣,它也須要聲明,即便它沒有在XSD模式中定義(但它存在於Spring Core中)。

對於構造函數參數名稱不可用的罕見狀況(一般發生在沒有調試信息的狀況下編譯字節碼),可使用參數索引:

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

因爲XML語法,須要在索引符號前加_,由於XML屬性名稱不能以數字開頭(即便某些IDE容許)。

在實踐中,構造函數解析機制在匹配參數方面很是有效,因此除非真的須要,建議使用參數名稱進行配置。

複合屬性名

當設置bean屬性時,可使用複合或嵌套屬性名稱,只要路徑上全部的組件(除了最後一個屬性)不爲null。考慮下面的bean定義:

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

foo bean 有一個fred屬性,fred有一個bob屬性,而bob有一個sammy屬性,而且最終的sammy屬性的值將被設置爲123。爲了使它可以工做,在bean被建立以後foo的fred屬性、fred的bob屬性必須不能爲空,不然NullPointerException異常將被拋出。

3 使用 depends-on

若是一個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" />

bean定義中的depend-on屬性能夠同時指定初始化時間依賴關係和相應的銷燬時間依賴關係(在singleton Bean的狀況下)。定義depends-on屬性的bean在被依賴的bean以前被銷燬。所以 depends-on 也能夠控制關​銷燬順序。

4 懶加載 bean

默認狀況下,ApplicationContext的實如今初始化過程當中建立和配置全部singleton bean。通常來講,這種預實例化是可取的,由於配置或環境中的錯誤會被當即發現,而不是幾個小時甚至幾天以後。若是不想採用這種行爲,能夠經過將bean定義標記爲懶加載來替代singleton的預實例化。懶加載bean通知IoC容器在首次請求時建立而不是容器啓動時。

在XML配置中,這種行爲被<bean/>元素的lazy-init屬性控制,例如:

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

當上述配置被ApplicationContext使用時,啓動時名爲lazy的bean不會被預先實例化,而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>

5 自動裝配協做者

Spring 容器能夠自動裝配協做的bean。能夠經過檢查ApplicationContext的內容來容許Spring自動解析您的bean的協做者。自動裝配有如下優勢:

  • 自動裝配能夠顯著減小指定屬性或構造函數參數的須要。(其餘機制,如bean模板在這方面也頗有價值。)
  • 自動裝配能夠隨着對象的發展而更新配置。例如,若是您須要向類添加依賴關係,則能夠自動知足該依賴關係,而無需修改配置。所以,自動裝配在開發過程當中特別有用,無需顯式指定而使代碼庫變得更加穩定。

在XML格式的配置元數據中,須要使用<bean/>元素的autowired屬性指定bean定義的自動裝配模式。自動裝配有四種模式。能夠指定每一個Bean的裝載方式,這樣Bean就知道如何加載本身的依賴。

  • no (默認)不裝配。bean的引用必須經過ref元素定義。對於較大的項目部署,不建議修改默認的配置,由於明確指定合做者給予更大的控制和清晰度。在某種程度上它記錄了系統的結構。

  • byName 使用name屬性自動裝配。Spring爲須要自動裝配的屬性查找與屬性具備相同名字的bean。例如,一個bean定義設置了按名字自動裝配,而且它包含一個master屬性(即它有setMaster(...)方法),Spring會查找名字爲master的bean定義,並使用它給屬性賦值。

  • byType 在容器中只有一個與屬性類型相同的bean存在時容許自動裝配這個屬性。若是有多個存在,會拋出致命的異常,提示對這個bean應使用byType模式的自動裝配。若是沒有相同類型的bean,則什麼都不作,該屬性不會被賦值。

  • constructor 相似於byType,但應用與構造函數參數。若是容器中沒有與構造函數參數類型匹配的bean,會產生一個致命的錯誤。

使用byType或constructor自動裝配模式,能夠裝配數組和強類型集合。在這種狀況下容器中全部符合類型的bean都會被自動裝配(做爲數組或集合的元素)。若是鍵的類型爲Sring,能夠自動裝配強類型Map。一個自動裝配的Map的值會包含容器中全部相同類型的實例,Map的鍵是相應類型的name。

能夠將自動裝配行爲與依賴關係檢查相結合,依賴關係檢查是在自動裝配完成後執行的。

自動裝配的侷限性和缺陷

自動裝載若是在整個的項目的開發過程當中使用,會工做的很好。可是若是不是全局使用,而只是用之來自動裝配幾個Bean的話,會很容易迷惑開發者。

下面是一些自動裝配的侷限性和缺陷:

  • 精確的property以及constructor-arg參數配置,會覆蓋掉自動裝配的配置。開發不可以自動裝配簡單屬性,好比基本類型字符串、Class類的對象(和這些類型組成的數組)。這是設計上的限制。

  • 自動裝配並有精確裝配準確。儘管如上表所述,Spring會很當心的避免在出現歧義狀況下猜想,可是由Spring管理的對象之間的關係再也不被明確記錄。

  • 對於從Spring容器生成文檔的工具,可能沒法得到裝配信息。

  • 容器中的多個bean定義可能由setter方法參數或構造函數參數的類型的匹配來進行自動裝配。對於數組、集合或者Map,這也許不是問題。然而對於單個值的依賴關係,這種模糊性是不能輕易解決的。若是沒有惟一的bean定義可用,則會拋出異常。

在後面的場景,開發者有以下的選擇:

  • 放棄自動裝配而使用精確裝配;
  • 在bean定義中經過配置autowire-candidate屬性爲false來阻止自動裝配;
  • 經過配置<bean/>元素的primary屬性爲true來指定這個bean爲主要的候選Bean;
  • 使用基於註釋的配置實現更細粒度的控制。

從自動裝配中排除一個bean

在每一個bean的基礎上,能夠將bean從自動裝配中排除。在Spring的XML格式中,將<bean/>元素的autowire-candidate屬性設置爲false;容器使特定的bean定義不可用於自動裝配架構(包括註釋配置,例如@Autowired)。

autowire-candidate屬性被設計爲僅影響基於類型的自動裝配。它不會影響名稱的顯式引用,即便指定的bean的autowired-candidate設置爲false。所以,若是名稱匹配,則經過名稱自動裝配將會注入一個bean。

開發者能夠經過模式匹配而不是Bean的名字來限制自動裝配的候選者。能夠在最上層的<beans/>元素的default-autowire-candidates屬性中來配置多種模式。好比,限制自動裝配候選者的名字以Repository結尾,能夠設置爲*Repository。若是須要配置多種模式,只須要用逗號分隔開便可。固然Bean中若是配置了autowire-candidate的話,這個信息擁有更高的優先級,模式匹配規則不會應用與這些bean。

這項技術有助於配置那些不須要由自動裝配注入到其它bean中的bean。一個被排除的bean不意味着它自身不能使用自動裝配。相反,僅僅是它自身並不是自動裝配其它bean的候選者。

方法注入

在大多數應用場景中,容器中的大多數bean都是singleton。當一個singleton bean須要與另外一個singleton bean協做時,或者一個非singleton bean須要與另外一個非singleton bean協做時,一般經過將一個bean定義爲另外一個bean的屬性來處理依賴關係。當bean生命週期不一樣時,會出現問題。假設單例bean A須要使用非單例(prototype)bean B,也許在A的每一個方法調用上。容器只建立單例bean A一次,所以只能得到一個設置屬性的機會。容器不能在每次須要的時候爲bean A提供一個新的bean B實例。

一種解決方案是放棄一部分控制反轉。能夠經過實現ApplicationContextAware接口使 Bean A能夠看到ApplicationContext,而且經過對容器的getBean(「B」)調用在每次bean A須要B時獲取B的實例(一般是新的)。如下是這種方法的一個例子:

// 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;
    }
}

上述方法是不可取的,由於業務代碼可見ApplicationContext並耦合到Spring框架。方法注入是Spring IoC容器的一個先進的功能,能夠以乾淨的方式處理這種狀況。

查詢方法注入

查詢方法注入Spring容器覆蓋它管理的bean的方法、來返回查找的另外一個容器中的Bean的一種能力。通常在上述場景中查找返回prototype bean。Spring框架經過使用CGLIB庫中的字節碼生成功能來動態生成覆蓋該方法的子類來實現此方法注入。

  • 爲了能使這種動態子類方法工做,Spring容器要動態生成子類的父類不能爲final,被覆蓋的方法也不能爲final。
  • 單元測試具備抽象方法的類須要您本身對類進行子類化,並提供抽象方法的存根實現。
  • 組件掃描也須要具體的方法,這須要具體的類來進行。
  • 另外一個關鍵的限制是,查找方法不適用於工廠方法,不適用於在配置類中的@Bean方法,由於在該狀況下容器不負責建立實例,所以沒法建立運行時生成的子類。

下面代碼片斷中的CommandManager類,Spring容器將動態地覆蓋createCommand()方法。CommandManager類將不會有任何Spring依賴關係:

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();
}

須要注入的方法須要有如下的簽名格式:

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

若是方法是抽象的,動態生成的子類會實現這個方法;若是方法不是抽象的,動態生成的子類會覆蓋這個方法。

<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" 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="myCommand"/>
</bean>

標識爲commandManager的bean只要須要myCommand bean的新實例,就調用它本身的方法createCommand()。必須部署myCommand bean做爲prototype。若是它是singleton,則每次返回myCommand bean的同一個實例。

或者在基於註釋的組件模型中,能夠經過@Lookup註釋聲明一個查找方法:

public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    protected abstract Command createCommand();
}

或者,更爲慣用的是,針對lookup方法的返回值的類型來解析目標bean:

public abstract class CommandManager {

    public Object process(Object commandState) {
        MyCommand command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup
    protected abstract MyCommand createCommand();
}

請注意,一般會使用具體的Lookup方法,以使它們與Spring的組件掃描規則兼容(抽象類默認狀況下被忽略)。此限制不適用於明確注入或明確導入bean類的狀況。

另外一種訪問不一樣範圍的目標bean的方法是ObjectFactory / Provider注入點。

還可使用ServiceLocatorFactoryBean(在org.springframework.beans.factory.config包中)。

任意方法替換

相比於查找方法注入,使用另外一個方法實現替換bean中任意方法是一種不經常使用的方法注入形式。

使用基於XML的配置元數據,可使用replacement-method元素將現有的方法實現替換。考慮下面的類,想要替換名爲computeValue的方法:

public class MyValueCalculator {

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

    // some other methods...

}

實現org.springframework.beans.factory.support.MethodReplacer接口的類提供了新的方法定義。

/**
 * 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 ...;
    }
}

部署原始類並指定方法覆蓋的bean定義以下所示:

<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 />元素中使用一個或多個<arg-type />元素來指定被覆蓋方法的簽名。只有當方法重載時,參數的簽名纔是必需的。爲方便起見,參數的類型字符串多是徹底限定類型名稱的子字符串。例如,如下所有匹配java.lang.String:

java.lang.String
String
Str

因爲參數的數量一般足以區分每一個可能的選擇,因此通常只需鍵入與參數類型匹配的最短字符串便可。

相關文章
相關標籤/搜索