此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10! |
依赖注入
依赖注入 (DI) 是对象定义其依赖关系的过程 (即,它们使用的其他对象)仅通过构造函数参数, 工厂方法的参数,或在对象实例上设置的属性 它是从工厂方法构造或返回的。然后容器注入这些 依赖项。这个过程从根本上来说是相反的(因此 控制实例化的 bean 本身的名称 Inversion of Control) 或通过使用类的直接构造来自行定位其依赖项,或 服务定位器模式。
DI 原理的代码更干净,当对象 提供它们的依赖项。该对象不查找其依赖项,而是查找 不知道依赖项的位置或类。因此,您的课程变得更加轻松 测试,特别是当依赖项依赖于接口或抽象基类时, 这允许在单元测试中使用存根或模拟实现。
DI 存在两个主要变体:基于构造函数的依赖注入和基于 Setter 的依赖注入。
基于构造函数的依赖注入
基于构造函数的 DI 是通过容器调用具有
参数数,每个参数表示一个依赖项。调用static
工厂方法
与构造 bean 的特定参数几乎是等价的,并且这个讨论
将参数处理为构造函数和static
factory 方法类似。这
以下示例显示了一个只能使用构造函数注入依赖项的类
注射:
-
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 时,类型是已知的,并且可以发生匹配(就像
case 与前面的示例)。当使用简单类型时,例如<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 是通过容器调用 setter 方法来完成的
调用无参数构造函数或无参数构造函数后的 beanstatic
factory 方法设置为
实例化你的 bean。
以下示例显示了一个只能使用 pur 塞特注射。此类是传统的 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 用于 Bean
管理。在某些依赖项已经存在后,它还支持基于 setter 的 DI
通过构造函数方法注入。您可以以
一个BeanDefinition
,您可以将其与PropertyEditor
instances 设置为
将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户无法工作
直接(即以编程方式)而不是 XML 使用这些类bean
定义、带注释的组件(即用@Component
,@Controller
,依此类推),或@Bean
基于 Java 的方法@Configuration
类。
然后,这些源在内部转换为BeanDefinition
并且习惯于
加载整个 Spring IoC 容器实例。
依赖关系解析过程
容器执行 bean 依赖关系解析,如下所示:
-
这
ApplicationContext
使用配置元数据创建和初始化 描述了所有的豆子。配置元数据可以通过 XML、Java 代码或 附注。 -
对于每个 bean,其依赖关系以属性、构造函数的形式表示 参数,或静态工厂方法的参数(如果您使用它而不是 法式构造函数)。当 bean 是 实际创建。
-
每个属性或构造函数参数都是要设置的值的实际定义,或者 对容器中另一个 bean 的引用。
-
作为值的每个属性或构造函数参数都从其指定的 format 设置为该属性或构造函数参数的实际类型。默认情况下,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 被实例化(如果它不是 预实例化的单例),其依赖项被设置,相关生命周期 方法(例如配置的 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
factory 方法返回对象的实例:
<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 方法(尽管在本例中是)。实例(非静态)factory 方法可以以基本相同的方式使用(除了从factory-bean
属性而不是class
属性),因此我们此处不讨论这些细节。