Spring的依赖注入方式(官网翻译)

本文详细介绍了Spring框架中的依赖注入(DI)原理,包括基于构造函数和基于设置器的两种方式。DI使得代码更加简洁,提高了测试能力。构造函数注入通过构造函数参数匹配、类型匹配、索引指定或名称指定来解析依赖。设置器注入则在bean实例化后通过setter方法注入依赖。推荐使用构造函数处理强制依赖,设置器处理可选依赖。Spring容器在创建bean时会解析并验证依赖关系,处理循环依赖问题。
摘要由CSDN通过智能技术生成


依赖项注入 (DI) 是一个过程,对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回对象后在对象实例上设置的属性来定义其依赖关系(即它们使用的其他对象)。然后,容器在创建 bean 时注入这些依赖项。此过程从根本上说是 bean 本身的反向(因此名称,即控制反转),它通过使用类的直接构造或服务定位器模式控制其依赖项本身的实例化或位置。

代码在 DI 原则下更简洁,当对象及其依赖关系提供时,分离更有效。对象不查找其依赖项,并且不知道依赖项的位置或类。因此,类更易于测试,特别是当依赖关系位于接口或抽象基类上时,这些基类允许在单元测试中使用存根或模拟实现。

DI存在于两个主要变体中:基于构造函数的依赖项注入和基于Setter 的依赖注入。

基于构造函数的依赖注入

基于构造函数的 DI 由调用具有许多参数的构造函数的容器完成,每个参数表示依赖项。调用具有特定参数来构造 bean 的工厂方法几乎等效,本讨论将参数处理到构造函数和工厂方法类似。下面的示例显示一个类,该类只能使用构造函数注入注入的依赖项:

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,对容器特定的接口、基类或注释没有依赖关系。

构造函数参数解析

构造函数参数解析匹配通过使用参数的类型发生。如果 bean 定义的构造函数参数中不存在潜在的歧义,则在 bean 定义中定义构造函数参数的顺序就是在实例化 bean 时向相应的构造函数提供这些参数的顺序。请考虑以下类:

package x.y;

public class ThingOne {

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

假设 和 类与继承无关,则不存在潜在的歧义。因此,以下配置工作正常,无需在元素中显式指定构造函数参数索引或类型。

<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 时,该类型是已知的,并且可能发生匹配(如上例所示)。使用简单类型(如 ,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;
    }
}

构造函数参数类型匹配

在上述方案中,如果使用 属性显式指定构造函数参数的类型,则容器可以使用与简单类型匹配的类型。如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

构造函数参数索引

可以使用 属性显式指定构造函数参数的索引,如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg index="0" value="7500000"/>
    <constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义之外,指定索引还解决构造函数具有两个相同类型的参数的歧义。

构造函数参数名称

还可以使用构造函数参数名称消除值歧义,如下例所示:

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

请记住,若要开箱即用,必须使用启用调试标志编译代码,以便 Spring 可以从构造函数查找参数名称。如果不能或不想使用调试标志编译代码,可以使用 @ConstructorProperties JDK 注释显式命名构造函数参数。然后,示例类必须如下所示:

基于设置的依赖注入

基于 Setter 的 DI 由容器调用 setter 方法完成,方法是在调用无参数构造函数或无参数工厂方法以实例化 bean 之后完成的。static

下面的示例显示一个类,该类只能使用纯 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...
}

支持基于构造函数和基于设置器的 DI,用于它管理的 bean。它还支持在通过构造函数方法注入某些依赖项后基于 setter 的 DI。以 的形式配置依赖项,将其与实例结合使用,将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户不直接使用这些类(即以编程方式使用),而是使用 XML 定义、注释组件(即使用 、 等注释的类)或基于 Java 的类中的方法。然后,这些源在内部转换为 实例,并用于加载整个 Spring IoC 容器实例。

基于构造函数或基于设置器的 DI?

由于可以混合使用基于构造函数的DI和基于setter的DI,因此将构造函数用于强制性依赖项并将setter方法或配置方法用于可选依赖性是一个很好的经验法则。注意,可以在setter方法上使用@Required批注,以使该属性成为必需的依赖项。但是,最好使用带有参数的程序验证的构造函数注入。

Spring团队通常提倡使用构造函数注入,因为它可以让您将应用程序组件实现为不可变对象,并确保不存在必需的依赖项。此外,注入构造函数的组件始终以完全初始化的状态返回到客户端(调用)代码。附带说明一下,大量的构造函数参数是一种不好的代码味道,这意味着该类可能承担了太多的职责,应该对其进行重构以更好地解决关注点之间的分离。

Setter注入主要应仅用于可以在类中分配合理的默认值的可选依赖项。否则,必须在代码使用依赖项的任何地方执行非空检查。 setter注入的一个好处是,setter方法使该类的对象在以后可以重新配置或重新注入。因此,通过JMX MBean进行管理是用于setter注入的引人注目的用例。

使用最适合特定班级的DI风格。有时,在处理您没有源代码的第三方类时,将为您做出选择。例如,如果第三方类未公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。

依赖性解析过程

容器执行 bean 依赖项解析,如下所示:

  • 使用描述所有 bean 的配置元数据创建和初始化。配置元数据可以通过 XML、Java
    代码或注释指定。ApplicationContext

  • 对于每个 bean,其依赖关系以属性、构造函数参数或静态工厂方法的参数的形式表示(如果使用该依赖项而不是普通构造函数)。当实际创建
    bean 时,这些依赖项将提供给 bean。

  • 每个属性或构造函数参数都是要设置的值的实际定义,或对容器中另一个 bean 的引用。

  • 作为值的每个属性或构造函数参数都将其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring
    可以将字符串格式提供的值转换为所有内置类型,如 、、 等。intlongStringboolean

在创建容器时,Spring 容器将验证每个 bean 的配置。但是,在实际创建 bean 之前,不会设置 bean 属性本身。创建容器时,将创建单元范围并设置为预实例化(默认值)的 Bean。范围在 Bean作用域中定义。否则,仅在请求 bean 时创建 bean。创建 bean 可能导致创建 bean 的图形,因为创建和分配 bean 的依赖项及其依赖项(等等)。请注意,这些依赖项之间的分辨率不匹配可能会晚出现 ,也就是说,在首次创建受影响的 bean 时。

循环依赖项
如果使用主要构造函数注入,则可以创建无法解决的循环依赖关系方案。

例如:A 类需要类 B 的实例,通过构造函数注入,B 类需要类 A 的实例,通过构造函数注入。如果为类 A 和 B 配置 bean 以相互注入,Spring IoC 容器将在运行时检测此循环引用,并引发 。BeanCurrentlyInCreationException

一个可能的解决方案是编辑要由设置器而不是构造函数配置的一些类的源代码。或者,避免构造函数注入,只使用设置注入。换句话说,虽然不建议这样做,但您可以使用 setter 注入来配置循环依赖关系。

与典型情况(没有循环依赖关系)不同,bean A 和 bean B 之间的循环依赖关系迫使在完全初始化之前将其中一个豆类注入另一个豆类(典型的鸡和蛋场景)。

你一般可以相信Spring做正确的事。它检测配置问题,例如在容器加载时对不存在的 bean 引用和循环依赖项。当实际创建 bean 时,Spring 将设置属性并尽可能晚地解析依赖项。这意味着,如果创建对象或对象依赖项之一出现问题,则正确加载的 Spring 容器以后在请求对象时可能会生成异常,例如,Bean 由于缺少或无效的属性而引发异常。这种可能延迟的可见性某些配置问题是为什么实现默认实例化单元豆。在实际需要之前,要创建这些 bean,需要花费一些前期时间和内存,在创建 时(而不是以后)发现配置问题。您仍然可以重写此默认行为,以便单元 bean 以懒洋洋方式初始化,而不是预先实例化。ApplicationContextApplicationContext

如果不存在循环依赖关系,则当一个或多个协作 bean 被注入从属 bean 时,在将每个协作 bean 注入到从属 bean 之前,将完全配置。这意味着,如果 bean A 依赖于 bean B,则 Spring IoC 容器在调用 bean A 上的 setter 方法之前完全配置 bean B。换句话说,bean 是实例化的(如果它不是预实例化的单元),则设置其依赖项,并调用相关的生命周期方法(如配置的 init方法或初始化Bean回调方法)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值