依赖注入
依赖注入 (DI) 是一种过程,对象仅通过构造函数参数、工厂方法的参数或在对象实例构造后或从工厂方法返回后设置在其对象实例上的属性来定义其依赖项(即,与其一起工作的其他对象)。然后,容器在创建 Bean 时注入这些依赖项。此过程从根本上是 Bean 本身控制其依赖项的实例化或位置的反向过程(因此称为控制反转),方法是使用类的直接构造或服务定位器模式。
使用 DI 原则可以使代码更简洁,并且当向对象提供其依赖项时,解耦更加有效。对象不查找其依赖项,也不了解依赖项的位置或类。因此,您的类更容易进行测试,尤其是在依赖项是接口或抽象基类时,这允许在单元测试中使用存根或模拟实现。
DI 存在两种主要变体:基于构造函数的依赖注入 和 基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来实现的,每个参数代表一个依赖项。使用特定参数调用静态工厂方法来构造 Bean 几乎等效,本讨论将构造函数和静态工厂方法的参数视为类似。以下示例显示了一个只能通过构造函数注入进行依赖注入的类。
-
Java
-
Kotlin
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...
}
// a constructor so that the Spring container can inject a MovieFinder
class SimpleMovieLister(private val movieFinder: MovieFinder) {
// business logic that actually uses the injected MovieFinder is omitted...
}
请注意,此类没有任何特殊之处。它是一个 POJO,不依赖于特定于容器的接口、基类或注释。
构造函数参数解析
构造函数参数解析匹配是通过使用参数的类型来完成的。如果 Bean 定义的构造函数参数不存在潜在的歧义,则 Bean 定义中定义构造函数参数的顺序就是这些参数在实例化 Bean 时提供给相应构造函数的顺序。考虑以下类:
-
Java
-
Kotlin
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
package x.y
class 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
-
Kotlin
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;
}
}
package examples
class ExampleBean(
private val years: Int, // Number of years to calculate the Ultimate Answer
private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything
)
构造函数参数类型匹配
在上述情况下,如果通过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
-
Kotlin
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
package examples
class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
基于 Setter 的依赖注入
基于 Setter 的依赖注入 (DI) 是通过容器在调用无参数构造函数或无参数static
工厂方法实例化 Bean 后,调用 Bean 上的 Setter 方法来实现的。
以下示例显示了一个只能通过使用纯 Setter 注入进行依赖注入的类。这个类是传统的 Java 类。它是一个 POJO,不依赖于容器特定的接口、基类或注解。
-
Java
-
Kotlin
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...
}
class SimpleMovieLister {
// a late-initialized property so that the Spring container can inject a MovieFinder
lateinit var 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 容器实例。
依赖项解析过程
容器执行 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 的依赖项及其依赖项的依赖项(依此类推)将被创建和分配。请注意,这些依赖项之间的解析不匹配可能会延迟显示——也就是说,在受影响的 Bean 首次创建时。
您通常可以相信 Spring 会做出正确的选择。它会在容器加载时检测配置问题,例如对不存在的 Bean 和循环依赖的引用。Spring 会尽可能延迟设置属性和解析依赖项,即在实际创建 Bean 时。这意味着,如果在创建该对象或其依赖项之一时出现问题(例如,由于缺少或无效的属性,Bean 抛出异常),已正确加载的 Spring 容器稍后可能会在您请求对象时生成异常。一些配置问题的这种潜在延迟可见性就是ApplicationContext
实现默认情况下预实例化单例 Bean 的原因。以预先花费一些时间和内存来创建这些 Bean(在它们实际需要之前)为代价,您可以在创建ApplicationContext
时发现配置问题,而不是以后。
如果没有循环依赖,当一个或多个协作 Bean 被注入到依赖 Bean 中时,每个协作 Bean 在被注入到依赖 Bean 之前都会被完全配置。这意味着,如果 Bean A 依赖于 Bean B,则 Spring IoC 容器会在调用 Bean A 上的 Setter 方法之前完全配置 Bean B。换句话说,Bean 将被实例化(如果它不是预实例化的单例),其依赖项将被设置,并且相关的生命周期方法(例如配置的 init 方法或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
类:
-
Java
-
Kotlin
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;
}
}
class ExampleBean {
lateinit var beanOne: AnotherBean
lateinit var beanTwo: YetAnotherBean
var i: Int = 0
}
在前面的示例中,声明 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
类:
-
Java
-
Kotlin
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;
}
}
class ExampleBean(
private val beanOne: AnotherBean,
private val beanTwo: YetAnotherBean,
private val i: Int)
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
-
Kotlin
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;
}
}
class ExampleBean private constructor() {
companion object {
// 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.
@JvmStatic
fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
val eb = ExampleBean (...)
// some other operations...
return eb
}
}
}
static
工厂方法的参数由<constructor-arg/>
元素提供,与实际使用构造函数完全相同。工厂方法返回的类的类型不必与包含static
工厂方法的类的类型相同(尽管在此示例中是相同的)。实例(非静态)工厂方法可以使用基本相同的方式(除了使用factory-bean
属性而不是class
属性之外),因此我们在此处不讨论这些细节。