Spring 核心技術(3)

接上篇:Spring 核心技術(2)html

version 5.1.8.RELEASEjava

1.4 依賴

典型的企業應用程序不會只包含單個對象(或 Spring 術語中的 bean)。即便是最簡單的應用程序也是由不少對象進行協同工做,以呈現出最終用戶所看到的有條理的應用程序。下一節將介紹如何從定義多個獨立的 bean 到實現對象之間相互協做從而實現可達成具體目標的應用程序。spring

1.4.1 依賴注入

依賴注入(DI)是一鍾對象處理方式,經過這個過程,對象只能經過構造函數參數、工廠方法參數或對象實例化後設置的屬性來定義它們的依賴關係(即它們使用的其餘對象)。而後容器在建立 bean 時注入這些依賴項。這個過程從本質上逆轉了 bean 靠本身自己經過直接使用類的構造函數或服務定位模式來控制實例化或定位其依賴的狀況,所以稱之爲控制反轉。編程

使用 DI 原則的代碼更清晰,當對象和其依賴項一塊兒提供時,解耦更有效。對象不查找其依賴項,也不知道依賴項的位置或類。所以,尤爲是依賴容許在單元測試中使用模擬實現的接口或抽象基類時,類會變得更容易測試。api

DI 存在兩個主要變體:基於構造函數的依賴注入基於 Setter 的依賴注入oracle

基於構造函數的依賴注入

基於構造函數的 DI 由容器調用具備多個參數的構造函數來完成,每一個參數表示一個依賴項。和調用具備特定參數的靜態工廠方法來構造 bean 幾乎同樣,本次討論用相同的方式處理構造函數和靜態工廠方法的參數。如下示例顯示了一個只能經過構造函數注入進行依賴注入的類:less

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

請注意,這個類沒有什麼特別之處。它是一個不依賴於特定容器接口、基類或註釋的POJO。ide

構造函數參數解析

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

package x.y;

public class ThingOne {

    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假設 ThingTwo 類和 ThingThree 類沒有繼承關係,則不存在潛在的歧義。那麼,如下配置能夠正常工做,你也不須要在 <constructor-arg/> 元素中顯式指定構造函數參數索引或類型。單元測試

<beans>
    <bean id="beanOne" class="x.y.ThingOne">
        <constructor-arg ref="beanTwo"/>
        <constructor-arg ref="beanThree"/>
    </bean>

    <bean id="beanTwo" class="x.y.ThingTwo"/>

    <bean id="beanThree" class="x.y.ThingThree"/>
</beans>

當引用另外一個 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 能夠從構造函數中查找參數名稱。若是您不能或不想使用 debug 標誌編譯代碼,則可使用 JDK 批註 @ConstructorProperties 顯式命名構造函數參數。而後,示例類必須以下所示:

package examples;

public class ExampleBean {

    // Fields omitted

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

基於 Setter 的依賴注入

基於 setter 的 DI 由容器在調用無參數構造函數或無參數靜態工廠方法來實例化 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 支持它管理的 bean 使用基於構造函數和基於 setter 的 DI。它還支持在經過構造函數方法注入了一些依賴項以後使用基於 setter 的 DI。你能夠以 BeanDefinition 的形式配置依賴項,能夠將其與 PropertyEditor 實例結合使用將屬性從一種格式轉換爲另外一種格式。然而,大多數 Spring 用戶不直接使用這些類(即編碼),而是用 XML bean 定義、註解組件(也就是帶 @Component@Controller等註解的類)或基於 Java 的 @Configuration 類中的 @Bean 方法。而後,這些源在內部轉換爲 BeanDefinition 實例並用於加載整個 Spring IoC 容器實例。

基於構造函數或基於 setter 的 DI?

因爲能夠混合使用基於構造函數和基於 setter 的 DI,所以將構造函數用於必填依賴項的同時 setter 方法或配置方法用於可選依賴項是一個很好的經驗法則。請注意, 在 setter 方法上使用 @Required 註解可以使屬性成爲必需的依賴項,然而更推薦使用編程式參數驗證的構造函數注入。

Spring 團隊一般提倡構造函數注入,由於它容許你將應用程序組件實現爲不可變對象,並確保所需的依賴項不是 null。此外,構造函數注入的組件始終以徹底初始化的狀態返回給客戶端(調用)代碼。旁註:大量的構造函數參數是一個糟糕的代碼味道,意味着該類可能有太多的責任,應該重構以更好地進行關注點的分離。

Setter 注入應僅用於可在類中指定合理默認值的可選依賴項。不然,必須在代碼使用依賴項的全部位置執行非空檢查。setter 注入的一個好處是 setter 方法使該類的對象能夠在之後從新配置或從新注入。所以,經過 JMX MBean 進行管理是 setter 注入的一個很好的使用場景。

使用對特定類最有意義的 DI 方式。有時在處理沒有源碼的第三方類時須要你本身作選擇。例如,若是第三方類沒有暴露任何 setter 方法,那麼構造函數注入多是惟一可用的 DI 方式。

依賴處理過程

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

  • 建立 ApplicationContext,以後根據描述全部 Bean 的配置元數據進行初始化。配置元數據能夠由 XML、Java代碼或註解指定。
  • 每一個 bean 的依賴關係都以屬性、構造函數參數或靜態工廠方法參數(若是使用它而不是普通的構造函數)的形式表示。實際建立 bean 時,會將這些依賴項提供給 bean。
  • 每一個屬性或構造函數參數都實際定義了須要設置的值或對容器中另外一個 bean 的引用。
  • 每一個屬性或構造函數參數都是一個從其指定的格式轉換爲該屬性或構造函數參數實際類型的值。默認狀況下,Spring 可以將提供的字符串格式轉換成全部內置類型的值,例如 intlongStringboolean等等。

Spring 容器在建立時驗證每一個 bean 的配置。可是在實際建立 bean 以前不會設置其屬性。做用域爲單例且被設置爲預先實例化(默認值)的 Bean 會在建立容器時建立。做用域在 Bean 做用域中定義。不然 bean 僅在須要時纔會建立。建立 bean 可能會致使不少 bean 被建立,由於 bean 的依賴項及其依賴項的依賴項(依此類推)被建立和分配。請注意,這些依賴項之間不匹配的問題可能會較晚才能被發現 - 也就是說,受影響的 bean 首次建立時。

循環依賴

若是您主要使用構造函數注入,有可能建立沒法解析的循環依賴場景。

例如:類 A 經過構造函數注入依賴類 B 的實例,而類 B 經過構造函數注入依賴類 A 的實例。若是將 A 類和 B 類的 bean 配置爲相互注入,Spring IoC 容器會在運行時檢測到此循環引用,並拋出 BeanCurrentlyInCreationException

一種可能的解決方案是編輯一些類的源代碼,將注入方式修改成 setter。或者是避免使用構造函數注入並僅使用 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 已經被實例化(若是它不是預先實例化的單例),依賴項已經被設置,並調用了相關的生命週期方法(如配置初始化方法InitializingBean 回調方法)。

依賴注入的示例

如下示例將基於 XML 的配置元數據用於基於 setter 的 DI。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"/>

如下示例展現了相應的 ExampleBean 類:

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

如下示例展現了相應的 ExampleBean 類:

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

如下示例展現了相應的 ExampleBean 類:

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 屬性),所以咱們不在此討論這些細節。

原文出處:https://www.cnblogs.com/aotian/p/11188225.html

相關文章
相關標籤/搜索