此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Framework 6.2.0! |
依赖关系注入
依赖关系注入 (DI) 是对象定义其依赖关系的过程 (即,它们与之相关的其他对象)仅通过构造函数参数, 工厂方法的参数,或在 它是从工厂方法构造或返回的。然后,容器会注入这些 dependencies 的 Dependencies 创建。这个过程基本上是相反的(因此 名称 Inversion of Control) 控制实例化的 bean 本身 或通过使用类的直接构造来定位其依赖项,或者 Service Locator 模式。
使用 DI 原则,代码更简洁,当对象 提供它们的依赖项。对象不查找其依赖项,并且 不知道依赖项的位置或类。因此,您的课程变得更加容易 进行测试时,特别是当依赖项位于接口或抽象基类上时, 允许在单元测试中使用 stub 或 mock 实现。
DI 存在于两个主要变体中:基于构造函数的依赖项注入和基于 Setter 的依赖项注入。
基于构造函数的依赖关系注入
基于构造函数的 DI 是通过容器调用带有
个参数,每个参数表示一个依赖项。调用static
工厂方法
使用特定的参数来构造 bean 几乎是等效的,并且此讨论
将参数视为构造函数和static
factory 方法。这
以下示例显示了一个只能使用 constructor 进行依赖项注入的类
注射:
-
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 定义中定义构造函数参数的顺序是 Order 其中,当 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
attribute 显式指定构造函数参数的索引,
如下例所示:
<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 注解来显式命名构造函数参数。sample 类将
然后必须查看如下:
-
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 方法完成的
调用 no-argument constructor 或 no-argument 之后的 beanstatic
factory 方法设置为
实例化你的 bean。
以下示例显示了一个只能使用 pure 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 的 bean it 的 DI
管理。它还支持基于设置程序的 DI,因为某些依赖项已经
通过 constructor 方法注入。您可以以
一个BeanDefinition
,您可以将其与PropertyEditor
实例设置为
将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户不工作
直接使用这些类(即以编程方式),而是使用 XMLbean
定义、带注释的组件(即,带@Component
,@Controller
等)、或@Bean
基于 Java 的方法@Configuration
类。
然后,这些源在内部转换为BeanDefinition
并且习惯了
加载整个 Spring IoC 容器实例。
依赖项解析过程
容器按如下方式执行 Bean 依赖关系解析:
-
这
ApplicationContext
使用配置元数据创建和初始化,该元数据 描述所有 Bean 的配置元数据可以通过 XML、Java 代码或 附注。 -
对于每个 bean,其依赖项以 properties 的形式表示,constructor arguments,或者 static-factory 方法的参数(如果你使用它而不是 normal 构造函数)。当 Bean 为 实际创建。
-
每个 property 或 constructor 参数都是要设置的值的实际定义,或者 对容器中另一个 bean 的引用。
-
作为值的每个属性或构造函数参数都从其指定的 format 设置为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如
int
,long
,String
,boolean
等。
Spring 容器在创建容器时验证每个 bean 的配置。 但是,在实际创建 Bean 之前,不会设置 Bean 属性本身。 创建 singleton 范围并设置为预实例化(默认值)的 bean 创建容器时。作用域在 Bean Scopes 中定义。否则 仅当请求 Bean 时,才会创建 Bean。创建 Bean 可能会导致 要创建的 bean 的图形,作为 bean 的依赖项及其依赖项' 创建并分配依赖项(依此类推)。请注意,之间的分辨率不匹配 这些依赖项可能会延迟显示 — 即,在首次创建受影响的 bean 时。
您通常可以相信 Spring 会做正确的事情。它检测配置问题,
例如对不存在的 bean 和循环依赖项的引用,在 Container
load-time 的Spring 会尽可能晚地设置属性并解析依赖关系,当
Bean 实际上是创建的。这意味着已加载的 Spring 容器
如果存在
创建该对象或其依赖项之一时,bean 会引发
异常。这可能会延迟
一些配置问题的可见性就是原因ApplicationContext
implementations by
默认的 pre-instantiate singleton bean。以一些前期时间和内存为代价
在实际需要这些 bean 之前创建它们,则会发现配置问题
当ApplicationContext
是创建的,而不是更晚的。您仍然可以覆盖此默认值
行为,以便单例 bean 惰性初始化,而不是 agerly
预实例化。
如果不存在循环依赖关系,则当一个或多个协作 bean 正在 注入到依赖 bean 中,每个协作 bean 都完全在 注入到依赖 bean 中。这意味着,如果 Bean A 依赖于 bean B 时,Spring IoC 容器会在调用 在 bean A 上执行 setter 方法。换句话说,bean 被实例化(如果它不是 预先实例化的单例),设置其依赖项,以及相关的生命周期 方法(例如配置的 init 方法或 InitializingBean 回调方法) 被调用。
依赖关系注入示例
以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。一个小 的一部分指定了一些 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 不是使用构造函数,而是 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
factory 方法由<constructor-arg/>
元素
与实际使用构造函数完全相同。类的类型为
返回的 API API 的 API API 的 API 请求
包含static
factory 方法(尽管在此示例中是 Factory 方法)。实例
(非静态)Factory 方法可以以基本相同的方式使用(除了
从使用factory-bean
属性而不是class
属性),因此我们
不要在这里讨论这些细节。