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类不是通过继承关联的,则不存在潜在的歧义。因此,以下配置可以正常工作,并且您无需在 元素中显式指定构造函数参数索引或类型。
<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时,类型是已知的,并且可以发生匹配(与前面的示例一样)。当使用简单的类型(例如)时 true,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工厂方法的参数由元素提供,就像实际使用构造函数一样。factory方法返回的类的类型不必与包含staticfactory方法的类具有相同的类型(尽管在此示例中是)。实例(非静态)工厂方法可以以本质上相同的方式使用(除了使用factory-bean属性代替使用class属性之外),因此在此不讨论这些细节。