Dependency Injection
依赖注入 (DI) 是一种对象仅通过构造器参数、工厂方法的参数或设置在从工厂方法构造或返回后对象实例上的属性来定义其依赖关系(即它们协同工作的其他对象)的过程。然后,容器在创建 bean 时注入这些依赖关系。此过程从根本上说是 bean 本身控制其依赖关系的实例化或位置的逆过程(因此称为控制反转),而 bean 是通过直接构建类或服务定位器模式来实现此过程的。
Dependency injection (DI) is a process whereby objects define their dependencies (that is, the other objects with which they work) only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse (hence the name, Inversion of Control) of the bean itself controlling the instantiation or location of its dependencies on its own by using direct construction of classes or the Service Locator pattern.
DI 原则使得代码更加简洁,当对象得到它的依赖项时,解耦变得更加有效。该对象无需查找其依赖项,且不知道依赖项的位置或类。因此,您的类变得更易于测试,特别是在依赖项位于接口或抽象基类上时,允许在单元测试中使用存根或模拟实现。
Code is cleaner with the DI principle, and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies and does not know the location or class of the dependencies. As a result, your classes become easier to test, particularly when the dependencies are on interfaces or abstract base classes, which allow for stub or mock implementations to be used in unit tests.
DI 存在两种主要变体:"@72" 和 "@73"。
DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.
Constructor-based Dependency Injection
基于构造器的 DI 将容器调用构造函数完成,带有大量参数,每个参数都表示一个依赖关系。调用具有特定参数的 static
工厂方法来构造 bean 几乎是等效的,并且此讨论对构造函数和 static
工厂方法的参数处理类似。以下示例展示了一个只能通过构造函数注入来依赖注入的类:
Constructor-based DI is accomplished by the container invoking a constructor with a
number of arguments, each representing a dependency. Calling a static
factory method
with specific arguments to construct the bean is nearly equivalent, and this discussion
treats arguments to a constructor and to a static
factory method similarly. The
following example shows a class that can only be dependency-injected with constructor
injection:
-
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,与特定于容器的接口、基类或注释无关。
Notice that there is nothing special about this class. It is a POJO that has no dependencies on container specific interfaces, base classes, or annotations.
Constructor Argument Resolution
构造函数参数解析匹配是通过使用参数的类型发生的。如果 Bean 定义的构造函数参数中不存在潜在的歧义,则Bean 定义中定义的构造函数参数的顺序是将这些参数提供给适当的构造函数的顺序,当 Bean 被实例化时。考虑以下类:
Constructor argument resolution matching occurs by using the argument’s type. If no potential ambiguity exists in the constructor arguments of a bean definition, the order in which the constructor arguments are defined in a bean definition is the order in which those arguments are supplied to the appropriate constructor when the bean is being instantiated. Consider the following class:
-
Java
-
Kotlin
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)
若 ThingTwo
和 ThingThree
类未通过继承产生关联,则不会存在任何潜在歧义。因此,以下配置正常运行,而且无需在 <constructor-arg/>
元素中显式指定构造函数的参数索引或类型。
Assuming that the ThingTwo
and ThingThree
classes are not related by inheritance, no
potential ambiguity exists. Thus, the following configuration works fine, and you do not
need to specify the constructor argument indexes or types explicitly in the
<constructor-arg/>
element.
<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 不能确定该值类型,因此,如果不借助其他帮助,将无法按类型进行匹配。考虑以下类:
When another bean is referenced, the type is known, and matching can occur (as was the
case with the preceding example). When a simple type is used, such as
<value>true</value>
, Spring cannot determine the type of the value, and so cannot match
by type without help. Consider the following class:
-
Java
-
Kotlin
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;
}
}
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
)
在前述场景中,如果明确指定构造函数参数类型,容器可以使用类型匹配和简单类型,如以下示例所示:
In the preceding scenario, the container can use type matching with simple types if
you explicitly specify the type of the constructor argument by using the type
attribute,
as the following example shows:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
你可以使用 index
属性明确指定构造函数参数的索引,如下面的示例所示:
You can use the index
attribute to specify explicitly the index of constructor arguments,
as the following example shows:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单值的歧义之外,指定索引可以解决构造函数有两个相同类型参数的歧义。
In addition to resolving the ambiguity of multiple simple values, specifying an index resolves ambiguity where a constructor has two arguments of the same type.
索引从 0 开始。 |
The index is 0-based. |
You can also use the constructor parameter name for value disambiguation, as the following example shows:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
请记住,为了使这项工作正常,你的代码必须在启用调试标志的情况下进行编译,以便 Spring 能够从构造函数中查找参数名称。如果你不能或不想在启用调试标志的情况下编译你的代码,可以使用 @ConstructorProperties JDK 注释来明确命名构造函数参数。然后,示例类的外观必须如下所示:
Keep in mind that, to make this work out of the box, your code must be compiled with the debug flag enabled so that Spring can look up the parameter name from the constructor. If you cannot or do not want to compile your code with the debug flag, you can use the @ConstructorProperties JDK annotation to explicitly name your constructor arguments. The sample class would then have to look as follows:
-
Java
-
Kotlin
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)
Setter-based Dependency Injection
基于 Setter 的 DI 是通过在你的 bean 上调用 no-argument 构造器或 no-argument 静态工厂方法实例化你的 bean 后由容器调用 setter 方法来完成的。
Setter-based DI is accomplished by the container calling setter methods on your
beans after invoking a no-argument constructor or a no-argument static
factory method to
instantiate your bean.
以下示例说明一个只能使用纯设置器注入来依赖项注入的类。此类是常规 Java。它是一个不依赖于特定于容器的接口、基类或注解的 POJO。
The following example shows a class that can only be dependency-injected by using pure setter injection. This class is conventional Java. It is a POJO that has no dependencies on container specific interfaces, base classes, or annotations.
-
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
实例,以实现不同格式之间的属性转换。但是,大多数 Spring 用户不会直接(即通过编程方式)使用这些类,而是使用 XML bean
定义、带注释的组件(即用 @Component
、@Controller
等进行注释的类)或者在基于 Java 的 @Configuration
类中使用的 @Bean
方法。这些来源会被随后内部转换为 BeanDefinition
实例,用以加载整个 Spring IoC 容器实例。
The ApplicationContext
supports constructor-based and setter-based DI for the beans it
manages. It also supports setter-based DI after some dependencies have already been
injected through the constructor approach. You configure the dependencies in the form of
a BeanDefinition
, which you use in conjunction with PropertyEditor
instances to
convert properties from one format to another. However, most Spring users do not work
with these classes directly (that is, programmatically) but rather with XML bean
definitions, annotated components (that is, classes annotated with @Component
,
@Controller
, and so forth), or @Bean
methods in Java-based @Configuration
classes.
These sources are then converted internally into instances of BeanDefinition
and used to
load an entire Spring IoC container instance.
由于你可以混合基于构造器的 DI 和基于调用的 DI,一个好的经验法则是使用构造器来处理强制性依赖关系,使用调用方法或配置方法来处理可选依赖关系。请注意,在调用方法上使用 @Autowired注释可用于将属性作为必需的依赖关系;但是,使用带有对参数进行编程验证的构造器注入是更可取的。
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Autowired annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.
Spring 团队通常提倡构造器注入,因为它允许你将应用程序组件实现为不可变对象并确保所需依赖项不为 null
。此外,构造器注入的组件总以完全初始化的状态返回给客户端(调用)代码。附带说明一下,大量的构造器参数是一种糟糕的代码味道,这意味着该类可能具有过多的责任,并且应进行重构以更好地解决适当的关注点分离。
The Spring team generally advocates constructor injection, as it lets you implement
application components as immutable objects and ensures that required dependencies
are not null
. Furthermore, constructor-injected components are always returned to the client
(calling) code in a fully initialized state. As a side note, a large number of constructor
arguments is a bad code smell, implying that the class likely has too many
responsibilities and should be refactored to better address proper separation of concerns.
调用注入主要只能用于可选的依赖关系,这些依赖关系可以在类中指定合理默认值。否则,该代码使用依赖关系的任何地方都必须执行非空检查。调用注入的一个好处是,调用方法可以使该类的对象能够将来重新配置或重新注入。因此,通过 JMX MBeans进行管理是调用注入的一个有力的用例。
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection.
根据特定类最有意义的方式使用 DI 样式。有时,在处理没有源代码的第三方类时,这个选择已经为你做好了。例如,如果第三方类不暴露任何 setter 方法,则构造器注入可能是 DI 唯一可用的形式。
Use the DI style that makes the most sense for a particular class. Sometimes, when dealing with third-party classes for which you do not have the source, the choice is made for you. For example, if a third-party class does not expose any setter methods, then constructor injection may be the only available form of DI.
Dependency Resolution Process
容器如下执行 Bean 依赖项解析:
The container performs bean dependency resolution as follows:
-
The
ApplicationContext
is created and initialized with configuration metadata that describes all the beans. Configuration metadata can be specified by XML, Java code, or annotations. -
For each bean, its dependencies are expressed in the form of properties, constructor arguments, or arguments to the static-factory method (if you use that instead of a normal constructor). These dependencies are provided to the bean, when the bean is actually created.
-
Each property or constructor argument is an actual definition of the value to set, or a reference to another bean in the container.
-
Each property or constructor argument that is a value is converted from its specified format to the actual type of that property or constructor argument. By default, Spring can convert a value supplied in string format to all built-in types, such as
int
,long
,String
,boolean
, and so forth.
Spring 容器在创建容器时会验证每个 bean 的配置。但是,只有在实际创建 bean 时才会设置 bean 属性。按默认进行预实例化(默认)并设置了单例作用域的 bean 会在创建容器时进行创建。作用域在 Bean Scopes中进行定义。否则,只有在需要时才会创建 bean。创建 bean 可能导致创建 bean 图,这是因为会创建并指定 bean 的依赖项及其依赖项的依赖项(依此类推)。请注意,这些依赖项之间的解析不匹配可能会在后期显示,也就是说,在首次创建受影响的 bean 时。
The Spring container validates the configuration of each bean as the container is created. However, the bean properties themselves are not set until the bean is actually created. Beans that are singleton-scoped and set to be pre-instantiated (the default) are created when the container is created. Scopes are defined in Bean Scopes. Otherwise, the bean is created only when it is requested. Creation of a bean potentially causes a graph of beans to be created, as the bean’s dependencies and its dependencies' dependencies (and so on) are created and assigned. Note that resolution mismatches among those dependencies may show up late — that is, on first creation of the affected bean.
如果你主要使用构造器注入,则有可能创建一个无法解析的循环依赖项场景。
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
例如:类 A 通过构造器注入需要类 B 的实例,而类 B 通过构造器注入需要类 A 的实例。如果你配置 Bean 使类 A 和 B 可以相互注入,则 Spring IoC 容器会在运行时检测到此循环引用,并抛出 BeanCurrentlyInCreationException
。
For example: Class A requires an instance of class B through constructor injection, and
class B requires an instance of class A through constructor injection. If you configure
beans for classes A and B to be injected into each other, the Spring IoC container
detects this circular reference at runtime, and throws a
BeanCurrentlyInCreationException
.
一种可能的解决方案是编辑某些类利用 Setter 而非构造器配置的源代码。或者,避免构造器注入并仅使用 Setter 注入。换句话说,虽然不建议,但你可以通过 Setter 注入配置循环依赖项。
One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
与典型情况(没有循环依赖项)不同,Bean A 和 Bean B 之间的循环依赖项会强制在 Bean 尚未完全初始化之前将其中一个 Bean 注入另一个(典型的先有鸡还是先有蛋的场景)。
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).
你通常可以相信 Spring 会采取正确的操作。在容器加载时间,它会检测到诸如对不存在的 Bean 的引用和循环依赖项之类的配置问题。Spring 会尽可能晚地设置属性并解析依赖项,即在实际创建 Bean 时。这意味着,如果 Spring 容器加载正常,则在你请求某对象时可能会生成一个异常,如果创建该对象或其依赖项之一存在问题——例如,Bean 由于缺少或无效的属性而抛出异常。某些配置问题的这种潜在延迟可见性正是 ApplicationContext
实现默认预实例化单例 Bean 的原因。这样做的代价是在实际需要这些 Bean 之前就花费了一些时间和内存来创建这些 Bean,但你可以在创建 ApplicationContext
时发现配置问题,而不是稍后。你仍然可以覆盖此默认行为,以便单例 Bean 延迟初始化,而不是热切预实例化。
You can generally trust Spring to do the right thing. It detects configuration problems,
such as references to non-existent beans and circular dependencies, at container
load-time. Spring sets properties and resolves dependencies as late as possible, when
the bean is actually created. This means that a Spring container that has loaded
correctly can later generate an exception when you request an object if there is a
problem creating that object or one of its dependencies — for example, the bean throws an
exception as a result of a missing or invalid property. This potentially delayed
visibility of some configuration issues is why ApplicationContext
implementations by
default pre-instantiate singleton beans. At the cost of some upfront time and memory to
create these beans before they are actually needed, you discover configuration issues
when the ApplicationContext
is created, not later. You can still override this default
behavior so that singleton beans initialize lazily, rather than being eagerly
pre-instantiated.
如果没有循环依赖项,当一个或多个协作 Bean 被注入到一个依赖 Bean 中时,每个协作 Bean 都会在注入到依赖 Bean 之前完全配置。这意味着,如果 Bean A 依赖于 Bean B,那么 Spring IOC 容器会在调用 Bean A 上的 setter 方法之前完全配置 Bean B。换句话说,Bean 被实例化(如果不是预例化的单例),它的依赖项被设置,并且会调用相关的生命周期方法(例如 "@74" 或 "@75")。
If no circular dependencies exist, when one or more collaborating beans are being injected into a dependent bean, each collaborating bean is totally configured prior to being injected into the dependent bean. This means that, if bean A has a dependency on bean B, the Spring IoC container completely configures bean B prior to invoking the setter method on bean A. In other words, the bean is instantiated (if it is not a pre-instantiated singleton), its dependencies are set, and the relevant lifecycle methods (such as a configured init method or the InitializingBean callback method) are invoked.
Examples of Dependency Injection
以下示例使用基于 XML 的配置元数据进行基于 Setter 的 DI。Spring XML 配置文件的一小部分按如下方式指定一些 Bean 定义:
The following example uses XML-based configuration metadata for setter-based DI. A small part of a Spring XML configuration file specifies some bean definitions as follows:
<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
类:
The following example shows the corresponding ExampleBean
class:
-
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:
In the preceding example, setters are declared to match against the properties specified in the XML file. The following example uses constructor-based 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
类:
The following example shows the corresponding ExampleBean
class:
-
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
构造器的参数。
The constructor arguments specified in the bean definition are used as arguments to
the constructor of the ExampleBean
.
现在考虑此示例的一个变体,其中,Spring 不是使用构造器,而是调用 static
工厂方法来返回对象的实例:
Now consider a variant of this example, where, instead of using a constructor, Spring is
told to call a static
factory method to return an instance of the object:
<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
类:
The following example shows the corresponding ExampleBean
class:
-
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
属性之外),因此我们在此不讨论这些细节。
Arguments to the static
factory method are supplied by <constructor-arg/>
elements,
exactly the same as if a constructor had actually been used. The type of the class being
returned by the factory method does not have to be of the same type as the class that
contains the static
factory method (although, in this example, it is). An instance
(non-static) factory method can be used in an essentially identical fashion (aside
from the use of the factory-bean
attribute instead of the class
attribute), so we
do not discuss those details here.