此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Framework 6.2.10spring-doc.cadn.net.cn

依赖注入

依赖注入 (DI) 是对象定义其依赖关系的过程 (即,它们使用的其他对象)仅通过构造函数参数, 工厂方法的参数,或在对象实例上设置的属性 它是从工厂方法构造或返回的。然后容器注入这些 依赖项。这个过程从根本上来说是相反的(因此 控制实例化的 bean 本身的名称 Inversion of Control) 或通过使用类的直接构造来自行定位其依赖项,或 服务定位器模式。spring-doc.cadn.net.cn

DI 原理的代码更干净,当对象 提供它们的依赖项。该对象不查找其依赖项,而是查找 不知道依赖项的位置或类。因此,您的课程变得更加轻松 测试,特别是当依赖项依赖于接口或抽象基类时, 这允许在单元测试中使用存根或模拟实现。spring-doc.cadn.net.cn

基于构造函数的依赖注入

基于构造函数的 DI 是通过容器调用具有 参数数,每个参数表示一个依赖项。调用static工厂方法 与构造 bean 的特定参数几乎是等价的,并且这个讨论 将参数处理为构造函数和staticfactory 方法类似。这 以下示例显示了一个只能使用构造函数注入依赖项的类 注射:spring-doc.cadn.net.cn

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 不依赖于特定于容器的接口、基类或注释。spring-doc.cadn.net.cn

构造函数参数解析

构造函数参数解析匹配通过使用参数的类型进行。如果没有 Bean 定义的构造函数参数中存在潜在的歧义,则 在 bean 定义中定义构造函数参数的顺序是顺序 其中,当 bean 是 正在实例化。考虑以下类:spring-doc.cadn.net.cn

package x.y;

public class ThingOne {

	public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
		// ...
	}
}
package x.y

class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)

假设ThingTwoThingThree类不通过继承相关,没有 存在潜在的歧义。因此,以下配置工作正常,而您不会 需要在<constructor-arg/>元素。spring-doc.cadn.net.cn

<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 无法确定值的类型,因此无法匹配 按类型在没有帮助的情况下。考虑以下类:spring-doc.cadn.net.cn

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属性 如以下示例所示:spring-doc.cadn.net.cn

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg type="int" value="7500000"/>
	<constructor-arg type="java.lang.String" value="42"/>
</bean>

构造函数参数索引

您可以使用index属性来显式指定构造函数参数的索引,如以下示例所示:spring-doc.cadn.net.cn

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg index="0" value="7500000"/>
	<constructor-arg index="1" value="42"/>
</bean>

除了解决多个简单值的歧义之外,指定索引还可以解决构造函数具有两个相同类型的参数的歧义。spring-doc.cadn.net.cn

该索引从 0 开始。

构造函数参数名称

您还可以使用构造函数参数名称来消除值歧义,如下所示示例显示:spring-doc.cadn.net.cn

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg name="years" value="7500000"/>
	<constructor-arg name="ultimateAnswer" value="42"/>
</bean>

请记住,要使此功能开箱即用,您的代码必须使用-parameters标志启用,以便 Spring 可以从构造函数中查找参数名称。 如果您不能或不想使用-parameters标志,您可以使用@ConstructorProperties JDK 注解来显式命名构造函数参数。示例类将 然后必须看如下:spring-doc.cadn.net.cn

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 方法来完成的 调用无参数构造函数或无参数构造函数后的 beanstaticfactory 方法设置为 实例化你的 bean。spring-doc.cadn.net.cn

以下示例显示了一个只能使用 pur 塞特注射。此类是传统的 Java。它是一个没有依赖关系的 POJO 在容器特定接口、基类或注释上。spring-doc.cadn.net.cn

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,您可以将其与PropertyEditorinstances 设置为 将属性从一种格式转换为另一种格式。但是,大多数 Spring 用户无法工作 直接(即以编程方式)而不是 XML 使用这些类bean定义、带注释的组件(即用@Component,@Controller,依此类推),或@Bean基于 Java 的方法@Configuration类。 然后,这些源在内部转换为BeanDefinition并且习惯于 加载整个 Spring IoC 容器实例。spring-doc.cadn.net.cn

基于构造函数还是基于 setter 的 DI?

由于您可以混合使用基于构造函数和基于 setter 的 DI,因此这是一个很好的经验法则 将构造函数用于强制依赖项和 setter 方法或配置方法 用于可选依赖项。请注意,在 setter 方法上使用 @Autowired 注释可用于使属性成为必需的依赖项; 但是,最好使用具有参数编程验证的构造函数注入。spring-doc.cadn.net.cn

Spring 团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不是null. 此外,构造函数注入的组件总是返回给客户端(调用)处于完全初始化状态的代码。顺便说一句,大量的构造函数arguments 是一种不好的代码气味,这意味着该类可能有太多的职责,应该进行重构以更好地解决关注点的适当分离问题。spring-doc.cadn.net.cn

Setter 注入主要应仅用于可选的依赖项,这些依赖项可以在类中分配合理的默认值。否则,必须进行非空检查在代码使用依赖项的任何地方执行。setter 注入的一个好处是setter 方法使该类的对象易于重新配置或重新注入 后。 因此,通过 JMX MBeans 进行管理是一个引人注目的setter 注入用例。spring-doc.cadn.net.cn

使用对特定类最有意义的 DI 样式。有时,在处理 对于您没有源代码的第三方类,您可以为您做出选择。 例如,如果第三方类不公开任何 setter 方法,则构造函数 注射可能是唯一可用的 DI 形式。spring-doc.cadn.net.cn

依赖关系解析过程

容器执行 bean 依赖关系解析,如下所示:spring-doc.cadn.net.cn

  • ApplicationContext使用配置元数据创建和初始化 描述了所有的豆子。配置元数据可以通过 XML、Java 代码或 附注。spring-doc.cadn.net.cn

  • 对于每个 bean,其依赖关系以属性、构造函数的形式表示 参数,或静态工厂方法的参数(如果您使用它而不是 法式构造函数)。当 bean 是 实际创建。spring-doc.cadn.net.cn

  • 每个属性或构造函数参数都是要设置的值的实际定义,或者 对容器中另一个 bean 的引用。spring-doc.cadn.net.cn

  • 作为值的每个属性或构造函数参数都从其指定的 format 设置为该属性或构造函数参数的实际类型。默认情况下,Spring 可以将以字符串格式提供的值转换为所有内置类型,例如int,long,String,boolean,依此类推。spring-doc.cadn.net.cn

Spring 容器在创建容器时验证每个 Bean 的配置。 但是,在实际创建 Bean 之前,不会设置 Bean 属性本身。 创建单例作用域并设置为预实例化(默认值)的 Bean 创建容器时。作用域在 Bean 作用域中定义。否则 仅在请求时才创建 bean。创建 Bean 可能会导致 要创建的 bean 的图形,作为 bean 的依赖项及其依赖项的 创建和分配依赖项(依此类推)。请注意,分辨率不匹配 这些依赖项可能会延迟出现,即在首次创建受影响的 Bean 时。spring-doc.cadn.net.cn

循环依赖关系

如果主要使用构造函数注入,则可能会创建不可解析的 循环依赖方案。spring-doc.cadn.net.cn

例如:类 A 需要通过构造函数注入来获取类 B 的实例,并且 类 B 需要通过构造函数注入来获取类 A 的实例。如果配置 要相互注入的类 A 和 B 的 bean,Spring IoC 容器 在运行时检测到此循环引用,并抛出一个BeanCurrentlyInCreationException.spring-doc.cadn.net.cn

一种可能的解决方案是编辑一些类的源代码,以配置 setter 而不是构造函数。或者,避免构造函数注入并使用 仅限入孵器注射。也就是说,虽然不推荐,但您可以配置 带有 setter 注入的循环依赖关系。spring-doc.cadn.net.cn

与典型情况(没有循环依赖关系)不同,循环依赖关系 在 bean A 和 bean B 之间强制其中一个 bean 在 本身完全初始化(经典的先有鸡还是先有蛋的场景)。spring-doc.cadn.net.cn

您通常可以相信 Spring 会做正确的事情。它检测配置问题, 例如对不存在的 bean 和循环依赖项的引用,在容器 加载时间。Spring 设置属性并尽可能晚地解析依赖关系,当 bean 实际上是被创造的。这意味着已加载的 Spring 容器 如果存在 创建该对象或其依赖项之一时出现问题——例如,bean 抛出 由于属性缺失或无效而导致的异常。这可能会延迟 某些配置问题的可见性是原因ApplicationContext实现者 默认预实例化单例 Bean。以一些前期时间和内存为代价 在实际需要之前创建这些 bean,您会发现配置问题 当ApplicationContext创建,而不是以后创建。您仍然可以覆盖此默认值 行为,以便单例 bean 延迟初始化,而不是急切地初始化 预实例化。spring-doc.cadn.net.cn

如果不存在循环依赖关系,则当一个或多个协作 Bean 注入到依赖 Bean 中时,每个协作 Bean 都是完全预先配置的 被注入到依赖的 bean 中。这意味着,如果 bean A 依赖于 bean B,Spring IoC 容器在调用 bean A 上的 setter 方法。换句话说,bean 被实例化(如果它不是 预实例化的单例),其依赖项被设置,相关生命周期 方法(例如配置的 init 方法InitializingBean 回调方法) 被调用。spring-doc.cadn.net.cn

依赖注入示例

以下示例将基于 XML 的配置元数据用于基于 setter 的 DI。一个小 Spring XML 配置文件的一部分指定了一些 bean 定义,如下所示:spring-doc.cadn.net.cn

<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类:spring-doc.cadn.net.cn

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:spring-doc.cadn.net.cn

<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类:spring-doc.cadn.net.cn

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-doc.cadn.net.cn

现在考虑这个例子的一个变体,其中 Spring 不是使用构造函数,而是 被告知要调用staticfactory 方法返回对象的实例:spring-doc.cadn.net.cn

<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类:spring-doc.cadn.net.cn

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/>元素 与实际使用构造函数完全相同。类的类型是 由工厂方法返回的类不必与 包含staticfactory 方法(尽管在本例中是)。实例(非静态)factory 方法可以以基本相同的方式使用(除了从factory-bean属性而不是class属性),因此我们此处不讨论这些细节。spring-doc.cadn.net.cn