7.4 Dependencies(依賴)

典型的企業應用程序不是由單個對象(或Spring說明中的bean)組成的。即便是最簡單的應用程序,也有幾個對象共同合做,展現最終用戶看到的一致性應用程序。下一節將介紹如何從一些獨立於徹底實現的應用程序的bean定義(對象協做實現目標)。html

7.4.1 Dependency Injection(依賴注入)

    依賴注入(DI)是對象定義其依賴性的過程,也就是他們工做的其餘對象,只有經過構造函數參數,工廠方法的參數,或者在從工廠方法構造或返回後在對象實例上設置的屬性。而後,容器在建立bean時注入這些依賴項。這個過程基本上是逆向的,所以,控制反轉(IoC)名稱自己就是經過使用直接構造類或者服務定位器模式來控制本身的依賴關係的實例化或位置。java

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

DI存在兩種主要的變體:基於構造函數的依賴注入(Constructor-based dependency injection )和基於Setter的依賴注入(Setter-based dependency injection)。編程

基於構造函數的依賴注入( Constructor-based dependency injection )

    基於構造函數的DI經過容器調用具備多個參數的構造函數完成,每一個參數表示一個依賴關係。調用一個具備特定參數的靜態工廠方法來構造bean幾乎是等效的,這個討論相似地將參數視爲一個構造函數和一個靜態的工廠方法。less

    如下示例顯示一個只能使用構造函數注入的依賴注入的類。請注意,這個類沒有什麼特別之處,它是一個POJO,它不依賴於容器特定的接口,基類或註釋。ide

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被實例化時將這些參數提供給適當的構造函數的順序。考慮下面的類:單元測試

package x.y;

public class Foo {

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

}

沒有潛在的歧義測試

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

,假設Bar和Baz類與繼承無關。所以,如下配置工做正常,您不須要在<constructor-arg />元素中顯式指定構造函數參數索引和/或類型。ui

    當引用另外一個bean時,類型是已知的,而且能夠進行匹配(如前面的例子所示)。當使用簡單的類型,例如<value> true </ value>時,Spring沒法肯定值的類型,所以沒法經過類型匹配,無需幫助。考慮下面的類:

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屬性顯式指定構造函數參數的類型,則容器可使用與簡單類型匹配的類型。例如:

<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開始。

    您還可使用構造函數參數名稱進行值消除歧義:

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

請記住,爲了使這個工做開箱即用,您的代碼必須在啓用調試標誌的狀況下進行編譯,以便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-based dependency injection)

    基於Setter的DI經過在調用無參數構造函數或無參數靜態工廠方法來實例化bean以後,經過容器調用bean的setter方法來實現。

    如下示例顯示一個只能使用純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的DI,用於其管理的bean。在一些依賴關係已經經過構造方法注入以後,它也支持基於setter的DI。您能夠以BeanDefinition的形式配置依賴項,該屬性與PropertyEditor實例結合使用,以將屬性從一種格式轉換爲另外一種格式。然而,大多數Spring用戶不直接使用這些類(即以編程方式),而是使用XML bean定義,註釋組件(即用@Component,@Controller等註釋的類)或@Bean方法在基於Java的@Configuration類中。而後將這些源內部轉換爲BeanDefinition的實例,並用於加載整個Spring IoC容器實例。

Constructor-based or setter-based DI?

因爲能夠混合基於構造函數和基於setter的DI,所以使用構造函數來強制依賴關係和setter方法或可選依賴關係的配置方法是一個很好的經驗法則。請注意,可使用setter方法上的@Required註釋來使屬性成爲必需的依賴關係。

  Spring團隊一般主張構造函數注入,由於它能夠將應用程序組件實現爲不可變對象,並確保所需的依賴關係不爲空。此外,構造函數注入的組件老是以徹底初始化的狀態返回給客戶端(調用)代碼。做爲一個附註,大量的構造函數論證是一個壞的代碼氣味,這意味着該類可能有太多的責任,應該重構以更好地解決問題的正確分離。

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

    有時候,當您處理沒有來源的第三方class時,您能夠選擇。  例如,若是第三方類不暴露任何setter方法,那麼構造函數注入多是DI的惟一可用形式。

Dependency resolution process

容器執行bean依賴解析以下:

    使用描述全部bean的配置元數據建立和初始化ApplicationContext。能夠經過XML,Java代碼或註釋指定配置元數據。

    對於每一個bean,若是使用它,而不是正常的構造函數,其依賴性以靜態工廠方法的屬性,構造函數參數或參數的形式表示。當bean實際建立時,這些依賴關係被提供給bean。

    每一個屬性或構造函數參數是要設置的值的實際定義,或對容器中另外一個bean的引用。

    做爲值的每一個屬性或構造函數參數都將從其指定的格式轉換爲該屬性或構造函數參數的實際類型。做爲值的每一個屬性或構造函數參數都將從其指定的格式轉換爲該屬性或構造函數參數的實際類型。

    建立容器時,Spring容器會驗證每一個bean的配置。可是,bean實際建立以前不會設置bean屬性。單個範圍(singleton-scoped)並設置爲預實例化 (pre-instantiated)(默認)的Bean將在建立容器時建立。 Scopes are defined in Section 7.5, 「Bean scopes」. ​​​​​​​不然,只有在請求時才建立該bean。​​​​​​​建立bean可能會致使建立一系列bean,由於建立和分配了bean的依賴關係及其依賴關係(等等)。​​​​​​​請注意,這些依賴關係中的解決方案不匹配可能會顯示較晚,即首次建立受影響的bean。​​​​​​​

循環依賴(circular dependencies)

    若是您主要使用構造函數注入,則能夠建立一個沒法解決的循環依賴性場景。

    例如:Class A經過構造函數注入須要一個類B的實例,B類須要一個經過構造函數注入的A類實例。若是將類A和B的bean配置爲彼此注入,則Spring IoC容器會在運行時檢測此循環引用,並拋出一個BeanCurrentlyInCreationException。

    一個可能的解決方案是編輯要由setter而不是構造函數配置的某些類的源代碼。或者,避免構造器注入和僅使用設定器注入。換句話說,雖然不推薦,您可使用setter注入來配置循環依賴。

    與典型的狀況(沒有循環依賴)不一樣,bean A和bean B之間的循環依賴關係強制其中一個Bean在被徹底初始化以前被注入另外一個bean(經典雞/雞蛋場景)。

    你通常能夠信任Spring作正確的事情。它在容器加載時檢測到配置問題,例如對不存在的bean和循環依賴項的引用。當bean實際建立時,Spring會盡量早地設置屬性並解析依賴關係。這意味着若是在建立該對象或其依賴關係時出現問題,則在請求對象時能夠正確加載的Spring容器能夠生成異常。​​​​​​​例如,bean因爲缺乏或無效的屬性而拋出異常。​​​​​​​某些配置問題的這種潛在的延遲可見性是爲何默認狀況下,ApplicationContext實現了單例bean以前的實例化。以實際須要以前建立這些bean的一些前期時間和內存爲代價,您能夠在建立ApplicationContext時發現配置問題,而不是之後。您仍然能夠覆蓋此默認行爲,以便單例bean將進行延遲初始化,而不是預先實例化。

    若是不存在循環依賴關係,當一個或多個協做bean被注入到依賴bean中時,每一個協做bean在被注入依賴bean以前被徹底配置。這意味着若是bean A對bean B有依賴關係,則Spring IoC容器在調用bean A的setter方法以前徹底配置bean B。換句話說,bean被實例化(若是不是預先實例化的單例),則設置它的依賴關係,並調用相關的生命週期方法(如配置的init方法或InitializingBean回調方法)。

    Examples of dependency injection

如下示例使用基於設置器的DI的基於XML的配置元數據。 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文件中指定的屬性相匹配。如下示例使用基於構造函數的DI:

<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的構造函數的參數。

如今考慮一個這個例子的變體,而不是使用一個構造函數,Spring被要求調用一個靜態工廠方法來返回對象的一個​​實例:

<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屬性而不是類屬性),所以這裏再也不討論細節。

相關文章
相關標籤/搜索