1. 程式人生 > >Spring依賴注入方式,為什麼Spring4.0提倡使用建構函式注入方式?

Spring依賴注入方式,為什麼Spring4.0提倡使用建構函式注入方式?

先回答題目中的問題:

基於建構函式或基於setter的DI?

何時使用構造注入,何時使用setter注入,經驗法則是:強制依賴用構造,可選依賴用Setter。注意,在settter方法上使用@Required註解即可令屬性強制依賴。 
Spring 團隊建議,構造注入的例項是不可變的,不為null的。此外,構造注入元件要將完全初始化後的例項返回給客戶端程式碼。還有,大量引數的建構函式是非常爛的,它意味著該類有大量的職責,得重構。 
setter注入主要用於可選依賴,類內部可以指定預設依賴。否則類內所有使用依賴的地方,都得進行非空校驗。setter注入的有個好處就是,類可以重配置或者再注入。因此,使用JMXMBeans進行管理的場景中,就非常適合setter注入。 
使用何種依賴注入方式,對於某些類,非常有意義。有時協同第三方類處理,沒有原始碼,由你來決定使用何種方式。比如,第三方類未暴露任何setter方法,那麼構造注入也許就是唯一的可行的注入方式了。

官網原文:

Constructor-based or setter-based DI?

Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Required

 annotation on a setter method can be used to make the property a required dependency.

The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell

, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.

Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.

 

下面詳細講述下依賴注入:

依賴注入(DI)是一個過程,通過這個過程,物件定義它們的依賴關係,即它們使用的其他物件,只能通過建構函式引數,工廠方法的引數或在構造或返回物件例項後在物件例項上設定的屬性。從工廠方法。然後容器在建立bean時注入這些依賴項。這個過程基本上是反向的,因此名稱 Inversion of Control(IoC),bean本身通過使用類的直接構造或服務定位器模式來控制其依賴項的例項化或位置。

使用DI原理的程式碼更清晰,當物件提供其依賴項時,解耦更有效。該物件不查詢其依賴項,也不知道依賴項的位置或類。因此,您的類變得更容易測試,特別是當依賴關係在介面或抽象基類上時,這允許在單元測試中使用存根或模擬實現。

DI存在兩個主要變體,基於建構函式的依賴注入基於Setter的依賴注入

基於建構函式的依賴注入

基於建構函式的 DI由容器呼叫具有多個引數的建構函式來完成,每個引數表示一個依賴項。呼叫static具有特定引數的工廠方法來構造bean幾乎是等效的,本討論同樣處理建構函式和static工廠方法的引數。以下示例顯示了一個只能通過建構函式注入進行依賴注入的類。請注意,此類沒有什麼特別之處,它是一個POJO,它不依賴於容器特定的介面,基類或註釋。

public  class SimpleMovieLister {

    // SimpleMovieLister依賴於MovieFinder 
    私有 MovieFinder movieFinder;

    //一個建構函式,以便Spring容器可以注入一個MovieFinder 
    public SimpleMovieLister(MovieFinder movieFinder){
         this .movi​​eFinder = movieFinder;
    }

    //省略了實際使用注入的MovieFinder的業務邏輯... 

建構函式引數解析

使用引數的型別進行建構函式引數解析匹配。如果bean定義的建構函式引數中不存在潛在的歧義,那麼在bean定義中定義建構函式引數的順序是在例項化bean時將這些引數提供給適當的建構函式的順序。

package x.y;

public class Foo {

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

假設Bar並且Baz類與繼承無關,則不存在潛在的歧義。因此,以下配置工作正常,您不需要在<constructor-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無法確定值的型別,因此無法在沒有幫助的情況下按型別進行匹配。

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標誌編譯程式碼(或者不希望),則可以使用 @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的 DI是在呼叫無引數建構函式或無引數static工廠方法來例項化bean之後,通過容器呼叫bean上的setter方法來完成的。

以下示例顯示了一個只能使用純setter注入進行依賴注入的類。這個類是傳統的Java。它是一個POJO,它不依賴於容器特定的介面,基類或註釋。

public  class SimpleMovieLister {

    // SimpleMovieLister依賴於MovieFinder 
    私有 MovieFinder movieFinder;

    //一個setter方法,以便Spring容器可以注入一個MovieFinder 
    public  void setMovieFinder(MovieFinder movieFinder){
         this .movi​​eFinder = movieFinder;
    }

    //省略了實際使用注入的MovieFinder的業務邏輯... 
}

ApplicationContext支架構造和基於setter方法的DI為它所管理的豆類。在通過建構函式方法注入了一些依賴項之後,它還支援基於setter的DI。您可以以a的形式配置依賴項,並將BeanDefinition其與PropertyEditor例項結合使用,以將屬性從一種格式轉換為另一種格式。然而,大多數Spring使用者不直接與這些類(即,程式設計),而是用XML bean 定義,註釋元件(即與註釋類@Component, @Controller等等),或@Bean在基於Java的方法@Configuration類。然後,這些源在內部轉換為例項BeanDefinition並用於載入整個Spring IoC容器例項。

 

Spring官網原文地址:https://docs.spring.io/spring/docs/4.3.20.RELEASE/spring-framework-reference/htmlsingle/#beans-constructor-injection