欢迎观看《Spring Framework实战》视频教程
典型的企业应用程序不是由单个对象(或Spring术语中的bean)组成。即使是最简单的应用程序也有几个对象协同工作,以呈现最终用户所认为的连贯应用程序。下一节将解释如何从定义多个独立的bean定义到一个完全实现的应用程序,在这个应用程序中,对象协作以实现目标。
本部分摘要
- 依赖注入
- 依赖项和配置详细信息
- 使用 depends-on
- 延迟初始化的 Bean
- 自动装配协作者
- 方法注入
-
-
- 依赖注入
-
-
依赖注入(Dependency injection)(DI)是一种过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖关系(即它们所使用的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程从根本上说是bean本身的逆过程(因此称为控制反转),bean通过使用类的直接构造或服务定位器模式来控制其依赖关系的实例化或位置。
DI原则使代码更清晰,当对象提供了它们的依赖关系时,解耦更有效。对象不查找其依赖关系,也不知道依赖关系的位置或类。因此,你的类变得更容易测试,特别是当依赖关系是在接口或抽象基类上时,这允许在单元测试中使用存根或模拟实现。
DI存在两种主要变体:基于构造函数的依赖注入和基于设置器的依赖注入。
-
-
-
-
- 基于构造函数的依赖注入
-
-
-
基于构造的依赖注入是由调用具有大量参数的构造函数的容器完成的,每个参数代表一个从属关系。要求static 工厂方法与特定的参数构造的Bean几乎是等价的,这个讨论将参数处理为构造函数和static 类似的工厂方法。下面的例子显示了一类只能依赖于结构注入的类:
Java
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final 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定义中定义构造函数参数的顺序是,当这些参数被实例化时,这些参数将被提供给适当的构造函数。考虑下列类别:
Java
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无法确定值的类型,因此在没有帮助的情况下无法按类型匹配。考虑以下类:
Java
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final 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>
请记住,为了开箱即用,您的代码必须在启用了-parameters标志的情况下编译,以便Spring可以从构造函数中查找参数名称。如果您不能或不想使用-parameters标志编译代码,可以使用@ConstructorProperties JDK注释显式命名构造函数参数。然后,示例类必须如下所示:
Java
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
-
-
-
-
- 基于设置器的依赖注入
-
-
-
基于Setter的DI是通过容器在调用无参数构造函数或无参数静态工厂方法实例化bean后调用bean上的Setter方法来实现的。
以下示例显示了一个只能通过使用纯setter注入进行依赖注入的类。这个类是传统的Java。它是一个POJO,不依赖于容器特定的接口、基类或注释。
Java
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支持基于构造函数和基于设置器的DI。在通过构造函数方法注入了一些依赖关系后,它还支持基于setter的DI。您可以以BeanDefinition的形式配置依赖关系,并将其与PropertyEditor实例结合使用,以将属性从一种格式转换为另一种格式。然而,大多数Spring用户并不直接使用这些类(即以编程方式),而是使用XML bean定义、带注释的组件(即用@Component、@Controller等注释的类)或基于Java的@Configuration类中的@bean方法。然后,这些源在内部转换为BeanDefinition的实例,并用于加载整个Spring IoC容器实例。
基于构造函数还是基于设置器的DI?
由于可以混合使用基于构造函数和基于设置器的DI,因此将构造函数用于强制依赖关系,将设置器方法或配置方法用于可选依赖关系是一个很好的经验法则。请注意,在setter方法上使用@Autowired注释可以使属性成为必需的依赖项;然而,构造函数注入和参数的编程验证是可取的。
Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖关系不为空。此外,构造函数注入的组件总是以完全初始化的状态返回给客户端(调用)代码。顺便说一句,大量的构造函数参数是一种难闻的代码气味,这意味着该类可能有太多的职责,应该重构以更好地解决适当的关注点分离问题。
Setter注入应该主要用于可选依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须在代码使用依赖关系的任何地方执行非空检查。setter注入的一个好处是,setter方法使该类的对象易于重新配置或稍后重新注入。因此,通过JMX MBean进行管理是setter注入的一个引人注目的用例。
使用对特定类最有意义的DI样式。有时,在处理您没有源代码的第三方类时,会为您做出选择。例如,如果第三方类不公开任何setter方法,那么构造函数注入可能是DI的唯一可用形式。
-
-
-
-
- 依赖解析过程
-
-
-
容器执行bean依赖解析,如下所示:
- ApplicationContext是使用描述所有bean的配置元数据创建和初始化的。配置元数据可以通过XML、Java代码或注释指定。
- 对于每个bean,它的依赖关系都以属性、构造函数参数或静态工厂方法的参数的形式表示(如果你使用它而不是普通的构造函数)。这些依赖关系在bean实际创建时提供给bean。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
- 作为值的每个属性或构造函数参数都会从其指定格式转换为该属性或构造函数的实际类型。默认情况下,Spring可以将以字符串格式提供的值转换为所有内置类型,如int、long、string、boolean等。
Spring容器在创建容器时验证每个bean的配置。但是,在实际创建bean之前,bean属性本身不会被设置。创建容器时,会创建单例作用域并设置为预实例化(默认)的Bean。作用域在Bean作用域中定义。否则,bean仅在被请求时创建。创建bean可能会导致创建bean图,因为bean的依赖关系及其依赖关系(等等)被创建和分配。请注意,这些依赖关系之间的解析不匹配可能会在稍后出现,即在首次创建受影响的bean时出现。
循环依赖关系
如果您主要使用构造函数注入,则可能会创建无法解决的循环依赖场景。
例如:类A通过构造函数注入需要类B的实例,类B通过构造函数注入要求类A的实例。如果将bean配置为类A和B相互注入,Spring IoC容器将在运行时检测到此循环引用,并抛出BeanCurrentlyCreationException。
一种可能的解决方案是编辑一些类的源代码,由setter而不是构造函数配置。或者,避免构造函数注入,只使用setter注入。换句话说,虽然不建议这样做,但您可以使用setter注入配置循环依赖关系。
与典型的情况(没有循环依赖)不同,bean a和bean B之间的循环依赖迫使其中一个bean在完全初始化之前注入另一个bean(经典的鸡和蛋场景)。
一般来说,你可以相信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被实例化(如果它不是预实例化的单例),其依赖关系被设置,相关的生命周期方法(如配置的init方法或InitializingBean回调方法)被调用。
-
-
-
-
- 依赖注入的例子
-
-
-
下面的示例使用基于xml的配置元数据为基于环境的di。一个SpringXML配置文件的一小部分指定了如下的一些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 班级:
Java
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;
}
}
在前面的示例中,设置者声明与XML文件中指定的属性匹配。下面的例子是使用基于建筑的数据交换:
<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 班级:
Java
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"/>
下面的示例显示相应的ExampleBean 班级:
Java
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;
}
}
辩论static 工厂方法由<constructor-arg/> 元素,和构造函数实际上被使用的完全一样。由工厂方法返回的类的类型不必与包含static 工厂方法(尽管在本例中是)。实例(非静态)工厂法可以使用基本相同的方式使用。factory-bean 代替了class 所以我们在这里不讨论这些细节。