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 属性),所以咱们不在此讨论这些细节。

相关文章
相关标签/搜索