乐趣区

关于spring:一起来读官方文档SpringIOC03

1.4。依存关系

典型的企业应用程序不坑你只蕴含单个对象或单个 bean。即便是最简略的应用程序,也有一些对象能够协同工作,以出现最终用户视为统一的应用程序。下一部分将阐明如何从定义多个独立的 Bean 定义到实现对象合作以实现目标的齐全实现的应用程序。

1.4.1。依赖注入

依赖注入 (Dependency injection,DI) 是一个过程,对象通过结构函数参数、工厂办法的参数或在结构或从工厂办法返回后在对象实例上设置的属性来定义它们的依赖项 (即与它们一起工作的其余对象)。
而后,容器在创立 bean 时注入这些依赖项。
这个过程基本上是 bean 自身的反向(因而得名,管制的反转),通过应用类的间接结构或服务定位器模式,bean 本人管制其依赖项的实例化或地位。

应用 DI 准则,代码会更清晰,并且当向对象提供它们的依赖时,解耦会更无效。对象不查找其依赖项,也不晓得依赖项的地位或类。因而,您的类变得更容易测试,特地是当依赖关系在接口或形象基类上时,它们容许在单元测试中应用 stub 或 mock 实现。

依赖注入有两种次要的变体: 基于结构器的依赖注入和基于 setter 的依赖注入。
基于构造函数的依赖注入

基于构造函数的 DI 是通过容器调用具备多个参数(每个参数代表一个依赖项)的构造函数来实现的。调用 static 带有特定参数的工厂办法来结构 Bean 简直是等效的,并且本次探讨将构造函数和 static 工厂办法的参数视为相似。以下示例显示了只能通过构造函数注入进行依赖项注入的类:

public class SimpleMovieLister {

    // SimpleMovieLister 依赖项 MovieFinder
    private MovieFinder movieFinder;

    // 构造函数,以便 Spring 容器可能注入 MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {this.movieFinder = movieFinder;}

}

留神,该类没有什么特地的。它是一个 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 {

    private int years;
    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 {@ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
基于 Setter 的依赖注入

基于设置器的 DI 是通过在调用无参数构造函数或无参数 static 工厂办法实例化 bean 之后,在 bean 上调用 setter 办法来实现的。

上面的示例显示只能通过应用纯 setter 注入来依赖注入的类。此类是惯例的 Java。它是一个 POJO,不依赖于容器特定的接口,基类或正文。

public class SimpleMovieLister {
    private MovieFinder movieFinder;

    public void setMovieFinder(MovieFinder movieFinder) {this.movieFinder = movieFinder;}
}
并不是写了上边的类并把这个 bean 注册进入 Spring 容器就能用 setter 注入
以下三种注册 bean 的形式 只有 autowire = byType, autowire=byName 才会应用 setter 注入 
autowire 默认属性就是 no,具体注入规定往后看
<bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="no"/>
<bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="byType"/>
<bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="byName"/>

ApplicationContext 为它治理的 bean 反对基于结构器和基于 setter 的 DI。在曾经通过构造函数办法注入了一些依赖项之后,它还反对基于 setter 的 DI。您以 BeanDefinition 的模式配置依赖项,您将它与 PropertyEditor 实例一起应用,以将属性从一种格局转换为另一种格局。然而,大多数 Spring 用户并不间接应用这些类 (即通过编程形式),而是应用 XML bean 定义、带正文的组件(即用 @Component、@Controller 等正文的类) 或基于 java 的 @Configuration 类中的 @Bean 办法。而后在外部将这些源转换为 BeanDefinition 的实例,并用于加载整个 Spring IoC 容器实例。

###### 基于构造函数或基于 setter 的 DI?因为能够混合应用基于构造函数的 DI 和基于 setter 的 DI,因而将构造函数用于强制性依赖项并将 setter 办法或配置办法用于可选依赖性是一个很好的教训法令。留神,能够 在 setter 办法上应用 @Required 批注,以使该属性成为必须的依赖项。然而,最好应用带有参数的程序验证的构造函数注入。Spring 团队通常提倡构造函数注入,因为它能够让您将应用程序组件实现为不可变对象,并确保不存在必须的依赖项 null。此外,注入构造函数的组件始终以齐全初始化的状态返回到客户端(调用)代码。附带阐明一下,大量的构造函数自变量是一种不好的代码味,这表明该类可能承当了太多的职责,应将其重构以更好地解决关注点拆散问题。Setter 注入次要应仅用于能够在类中调配正当的默认值的可选依赖项。否则,必须在代码应用依赖项的任何中央执行非空查看。setter 注入的一个益处是,setter 办法使该类的对象在当前能够重新配置或从新注入。因而,通过 JMX MBean 进行治理是用于 setter 注入的引人注目的用例。应用最适宜特定班级的 DI 格调。有时,在解决您没有源代码的第三方类时,将为您做出抉择。例如,如果第三方类未公开任何 setter 办法,则构造函数注入可能是 DI 的惟一可用模式。
依赖性解析过程

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

  • 用形容所有 bean 的配置元数据创立和初始化 ApplicationContext。配置元数据能够由 XML、Java 代码或正文指定。。
  • 对于每个 bean,其依赖关系都以属性,结构函数参数或 static-factory 办法的参数的模式示意(如果应用它而不是一般的构造函数)。在理论创立 Bean 时,会将这些依赖项提供给 Bean。
  • 每个属性或结构函数参数都是要设置的值的理论定义,或者是对容器中另一个 bean 的援用。
  • 作为值的每个属性或结构函数参数都将从其指定的格局转换为该属性或结构函数参数的理论类型。默认状况下,Spring 可能以 String 类型提供值转换成所有内置类型,比方 int,long,String,boolean 等等。

在创立容器时,Spring 容器验证每个 bean 的配置。
然而,bean 属性自身在理论创立 bean 之前是不会设置的。
在创立容器时,将创立单例范畴并设置为预实例化 (缺省值) 的 bean。
作用域在 Bean 作用域中定义。
否则,只在申请 bean 时创立它。创立一个 bean 可能会导致须要创立一系列的 bean 甚至是循环依赖的 bean,因为须要创立和调配 bean 的依赖项和 bean 的依赖项的依赖项 (等等)。
留神,这些依赖项之间的解析不匹配会在第一次创立受影响的 bean 时呈现。

###### 循环依赖
与典型的状况不同(没有循环依赖关系),bean A 和 bean B 之间的循环依赖关系迫使一个 bean 在齐全初始化之前注入另一个 bean(典型的先有鸡还是先有蛋的场景)。如果您次要应用构造函数注入,那么就会创立一个无奈解决的循环依赖场景。例如: 类 A 通过构造函数注入须要一个类 B 的实例,类 B 通过构造函数注入须要一个类 A 的实例。如果您为类 A 和类 B 配置了互相注入的 bean,那么 Spring IoC 容器在运行时检测到这个循环援用,并抛出 BeanCurrentlyInCreationException。一个可能的解决方案是编辑一些类的源代码,以便应用 setter 注入而不是结构器注入。或者,防止构造函数注入,只应用 setter 注入。换句话说,只管不倡议这样做,但您能够应用 setter 注入配置循环依赖关系。

您通常能够置信 Spring 会做正确的事件。
它在容器加载时检测 bean 不存在和循环依赖关系的问题。
Spring 当 bean 理论创立时会尽可能晚地设置属性并解析依赖项。
这意味着,正确加载的 Spring 容器在创建对象或其依赖项时呈现问题时,能够在申请对象时生成异样——例如,bean 因为短少或有效的属性而抛出异样。
某些配置问题的潜在提早可见性是 ApplicationContext 实现在默认状况下预实例化单例 bean 的起因。因为在理论须要之前创立这些 bean 须要破费一些后期工夫和内存,所以默认是在创立 ApplicationContext 时发现配置问题,而不是稍后发现。
您依然能够笼罩这个默认行为,以便单例 bean 能够惰性地初始化,而不是事后实例化。

如果不存在循环依赖关系,那么当一个或多个合作 bean 被注入到依赖 bean 中时,每个合作 bean 在被注入到依赖 bean 之前都已被齐全配置。
这意味着, 如果 bean A 依赖 bean B, Spring IoC 容器会在调用 bean A 的 setter 办法之前齐全配置 bean B。
换句话说,bean 实例化 (如果它不是一个单例事后实例化), 其设置依赖项, 相干的生命周期办法(如 InitializingBean init 办法或配置回调办法) 调用。

依赖注入的例子

以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。Spring XML 配置文件的一小部分指定了一些 bean 定义,如下所示:

此处 examoleBean 的 autowire 默认属性就是 no,手动指定了 属性对应的 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 调用 static 工厂办法以返回对象的实例:

<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 {

    private AnotherBean beanOne;
    private YetAnotherBean beanTwo;
    private int i;

    private ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
        this.beanOne = anotherBean;
        this.beanTwo = yetAnotherBean;
        this.i = i;
    }

    public static ExampleBean createInstance (AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {ExampleBean eb = new ExampleBean (...);
        // some other operations...
        return eb;
    }
}

static 工厂办法的参数由 <constructor-arg/> 元素提供,就像理论应用构造函数一样。factory 办法返回的类的类型不用与蕴含 staticfactory 办法的类具备雷同的类型(只管在此示例中是)。实例(非动态)工厂办法能够以实质上雷同的形式应用(除了应用 factory-bean 属性代替应用 class 属性之外),因而在此不探讨这些细节。

退出移动版