Spring 核心技术(3)

接上篇:Spring 核心技术(2)

version 5.1.8.RELEASE

1.4 依赖

典型的企业应用程序不会只包含单个对象(或 Spring 术语中的 bean)。即使是最简单的应用程序也是由很多对象进行协同工作,以呈现出最终用户所看到的有条理的应用程序。下一节将介绍如何从定义多个独立的 bean 到实现对象之间相互协作从而实现可达成具体目标的应用程序。

1.4.1 依赖注入

依赖注入(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) {
        // ...
    }
}

假设 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

时间: 2024-08-30 04:30:05

Spring 核心技术(3)的相关文章

Spring核心技术IoC容器(八)

本文针对自动装载的一些注解进行描述. 基于注解的容器配置 @Required注解 @Required注解需要应用到Bean的属性的setter方法上面,如下面的例子: public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } //

Spring核心技术

这是第二次看关于Spring的资料,因为刚开始学习Spring的时候是边看视频边学习的,所以更注重的是实现代码,但是对宏观的掌握还是不够,这次主要从宏观的角度来分析一下Spring. 什么是Spring Spring是分层的Java SE/EE应用一站式的轻量级开源框架,以IoC(Inverse of Control:反转控制)和AOP(AspectOriented Programming:面向切面编程)为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众

spring 核心技术

spring 核心技术1--控制反转或反向控制 (inversion control IOC) 当一个对象需要另一个对象时, 传统设计过程中, 需要通过调用者来创建被调用着的对象实例 但是在spring中, 创建被调用者的工作不再有调用者来完成,而是由spring IOC容器完成. DI(dependency injection) 依赖注入 就是组件之间的依赖关系有容器在运行期决定,形象的来说, 就是由容器动态的将某种依赖关系注入到组件中 容器就是 .xml配置文件 通过使用DI,当组件之间关系

Spring核心技术IoC容器(六)

前文已经描述了Bean的作用域,本文将描述Bean的一些生命周期作用,配置还有Bean的继承. 定制Bean 生命周期回调 开发者通过实现Spring的InitializeingBean和DisposableBean接口,就可以让容器来管理Bean的生命周期.容器会调用afterPropertiesSet()前和destroy()后才会允许Bean在初始化和销毁Bean的时候执行一些操作. JSR-250的@PostConstruct和@PreDestroy注解就是现代Spring应用生命周期回

Spring核心技术IoC容器(五)

前文概述了Spring的容器,Bean,以及依赖的一些信息,本文将描述一下Bean的作用域 Bean的作用域 当开发者定义Bean的时候,同时也会定义了具体如何创建Bean实例的步骤.这些步骤是很重要的,因为只有通过这些配置,开发者才能创建实例对象. 开发者不仅可以控制多种多样的依赖到Bean之中,也可以配置Bean的作用域.这种方法是非常强大而且弹性也非常好,开发者可以通过配置来指定对象的作用域,而不用在Java类层次上来配置.Bean可以配置多种作用域.Spring框架支持5中作用域,有三种

Spring核心技术IoC容器(四)

前面两篇文章描述了IoC容器中依赖的概念,包括依赖注入以及注入细节配置.本文将继续描述玩全部的依赖信息. 使用 depends-on 如果一个Bean是另一个Bean的依赖的话,通常来说这个Bean也就是另一个Bean的属性之一.多数情况下,开发者可以在配置XML元数据的时候使用<ref/>标签.然而,有时Bean之间的依赖关系不是直接关联的.比如:需要调用类的静态实例化器来出发,类似数据库驱动注册.depends-on属性会使明确的强迫依赖的Bean在引用之前就会初始化.下面的例子使用dep

Spring 核心技术(4)

接上篇:Spring 核心技术(3) version 5.1.8.RELEASE 1.4.2 依赖关系及配置详情 如上一节所述,你可以将 bean 属性和构造函数参数定义为对其他托管 bean(协作者)的引用,或者作为内联定义的值.Spring 基于 XML 的配置元数据为此目的支持子元素<property/>和<constructor-arg/>. 直接值(基本类型,字符串等) <property/>元素的 value 属性指定一个属性或构造器参数为可读的字符串.Sp

Spring 核心技术(7)

接上篇:Spring 核心技术(6) version 5.1.8.RELEASE 1.6 定制 Bean 的特性 Spring Framework 提供了许多可用于自定义 bean 特性的接口.本节将它们分组如下: 生命周期回调 ApplicationContextAware 和 BeanNameAware 其他 Aware 接口 1.6.1 生命周期回调 要与容器的 bean 生命周期管理进行交互,可以实现 Spring InitializingBean 和 DisposableBean 接口

Spring核心技术(二)——Spring的依赖及其注入

本文将继续前文,描述Spring IoC中的依赖处理. 依赖 一般情况下企业应用不会只有一个对象(或者是Spring Bean).甚至最简单的应用都要多个对象来协同工作来让终端用户看到一个完整的应用的.下一部分将解释开发者如何从仅仅定义单独的Bean,到让这些Bean在一个应用中协同工作. 依赖注入 依赖注入是一个让对象只通过构造参数,工厂方法的参数或者配置的属性来定义他们的依赖的过程.这些依赖也是对象所需要协同工作的对象.容器会在创建Bean的时候注入这些依赖.整个过程完全反转了由Bean自己