|
对于最新的稳定版本,请使用 Spring Framework 7.0.6! |
依赖注入
依赖注入(DI)是一种过程,其中对象通过构造函数参数、工厂方法的参数,或者在对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖项(即,它们所协作的其他对象)。当创建bean时,容器会注入这些依赖项。这个过程本质上是相反的(因此得名“控制反转”),与bean自身通过使用类的直接构造或服务定位器模式来控制其依赖项的实例化或定位完全不同。
使用DI原则,代码会更加整洁,当对象被提供依赖项时,解耦会更有效。对象不会查找其依赖项,也不会知道依赖项的位置或类。结果是,你的类更容易测试,特别是当依赖项是接口或抽象基类时,这允许在单元测试中使用模拟或桩实现。
DI 存在两种主要形式: 基于构造函数的依赖注入 和 基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的DI是通过容器调用一个构造函数来完成的,该构造函数具有多个参数,每个参数代表一个依赖项。使用特定参数调用static工厂方法来构造bean几乎是等效的,本讨论将构造函数的参数和static工厂方法的参数视为类似。下面的示例显示了一个只能通过构造函数注入进行依赖注入的类:
-
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>
请注意,为了让此功能开箱即用,您的代码必须使用调试标志进行编译,以便Spring可以从构造函数中查找参数名称。 如果您无法或不想使用调试标志编译代码,可以使用 @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)
基于设置的依赖注入
基于设置方法的DI是通过容器在调用无参数构造函数或无参数static工厂方法实例化你的bean之后调用你的bean上的设置方法来完成的。
以下示例显示了一个只能通过使用纯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 支持基于构造函数和基于setter的DI,用于管理它所管理的beans。在通过构造函数方法注入一些依赖项之后,它还支持基于setter的DI。您以 BeanDefinition 的形式配置依赖项,这与 PropertyEditor 实例一起使用,以将属性从一种格式转换为另一种格式。然而,大多数Spring用户并不直接(即编程方式)使用这些类,而是使用XML bean 定义、带注解的组件(即用 @Component、@Controller 等注解的类),或Java-based @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首次创建时。
通常可以信任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 的配置元数据进行基于 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 属性),因此我们在此不讨论这些细节。