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属性而不是类属性),所以这里再也不讨论细节。

相关文章
相关标签/搜索