依赖注入(DI)是一个处理对象的依赖的过程,换句话说就是处理与某个对象一起工作的其它对象。所依赖的对象可以通过构造参数、工厂方法参数以及设置对象实例的属性来定义。当一个对象被创建时,IOC 容器会注入它的依赖,这个过程与传统创建对象的方式是相反的故称为控制反转(IOC)。
 
使用DI后代码十分的简洁,并且在定义依赖时也能有效的解耦。对象不需要过多的去关注它依赖的对象,所以类更容易测试,特别是依赖于接口或者是抽象类时,可以在单元测试中使用stub或者mock实现。
 
DI存在两种主要的形式:基于构造器注入和Setter方法注入。
* * * * *
 
### **基于构造器注入**
构造注入通过容器提供指定的参数调用构造方法来完成,每个参数都称为依赖。这和通过提供给静态的工厂方法的参数来创建对象几乎是等价的。下面的样例展示了一个类通过构造方法来完成依赖注入的过程。
~~~
public class SimpleMovieLister {
// SimpleMovieLister依赖于MovieFinder
private MovieFinder movieFinder;
// Spring通过构造器注入MovieFinder实例
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
//使用被注入MovieFinder实例来完成的业务逻辑已被忽略
}
~~~
注意:这个类是普通的Java对象,不依赖于容器相关的接口、基类或者注解。
### **构造参数的识别**
构造参数的识别是通过类型匹配来完成的,如果没有模糊不清的构造参数,那么构造参数的定义顺序就是容器实例化Bean时提供参数给构造器的顺序。参考如下代码:
~~~
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
~~~
现在,参数都是明确的。假定`Bar`和`Baz`没有继承关系,因此下面的配置没有任何问题,你也不需要在`<constructor-arg/>`元素中指定参数的索引和类型。
~~~
<beans>
<bean id="foo" class="x.y.Foo">
<constructor-arg ref="bar"/>
<constructor-arg ref="baz"/>
</bean>
<bean id="bar" class="x.y.Bar"/>
<bean id="baz" class="x.y.Baz"/>
</beans>
~~~
当引用的Bean的类型是确定的,这能很好的进行参数匹配。如果使用的是简单类型,比如:`<value>true</value>`,Spring就不能判断其类型了,所以不能独自完成类型匹配。参考如下代码:
~~~
package examples;
public class ExampleBean {
//计算最终答案的年份
private int years;
//生活、宇宙、万物的答案
private String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
~~~
##### 1.构造参数类型匹配
在上面的例子中,如果你通过`type`属性指定参数的类型,那么容器可以很好的进行简单参数类型的类型匹配。参考如下代码:
~~~
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
~~~
##### 2.构造参数索引
使用`index`属性指定参数在构造器中的索引,参考如下代码:
~~~
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
~~~
> 如果不指定index的值,则默认以<constructor-arg/>出现的顺序为参数索引。
为了解决多个简单类型间的歧义性,可以通过指定值在构造参数中的索引。注意:索引从0开始。
##### *3.构造参数名*
你也可以指定值所对应构造器中的参数名来消除歧义:
~~~
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
~~~
注意:要使上述样例正常工作,必须启用调试标记来编译代码,这样Spring才能从构造器中查找到参数名。否则你必须使用`@ConstructorProperties` JDK注解来显式指定参数名,参考如下代码:
~~~
package examples;
public class ExampleBean {
// 忽略属性
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
~~~
*****
 
### **基于Setter方法注入**
容器通过无参构造方法或者无参静态工厂方法实例化Bean之后,再通过调用Setter方法来完成依赖注入。
下面的样例展示了一个类通过Setter方法来完成依赖注入。注意:这个类是普通的Java对象,不依赖于容器相关的接口、基类或者注解。
~~~
public class SimpleMovieLister {
// SimpleMovieLister依赖于MovieFinder
private MovieFinder movieFinder;
// Spring通过Setter方法注入MovieFinder实例
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
//使用被注入MovieFinder实例来完成的业务逻辑已被忽略
}
~~~
`ApplicationContext`支持构造注入和Setter方法注入,也支持通过构造参数注入某些依赖后再通过Setter注入其它依赖。配置的依赖存在于`BeanDefinition`中,可以结合`PropertyEditor`完成属性的转换。然而,大多开发者并不会直接使用这些类(即以编程的方式)而是使用XML、被注解的组件(即被`@Component`、 `@Controller`注解的类)以及带有`@Configuration`的类中`@Bean`注解的方法来定义Bean,这些配置在内部被转换为`BeanDefinition`实例并被用来加载整个Spring IOC容器。
<p style="margin-bottom:0px"> </p>
>### :-: **使用构造注入还是Setter方法注入?**
>
> 尽管可以使用混合注入,但是通过**构造方法完成必要依赖的注入**,通过**Setter方法完成可选依赖的注入**是一个好的编程方式。注意:在Setter方法上标注`@Required`([1.9.1.@Required](1.9.1.Required.md))注解使得属性为必需依赖。不过,使用构造注入(带有编程式参数验证)更可取。
> **Spring团队提倡使用构造注入**,原因如下:
> 1.该方式可以使得依赖为不可变对象并且不会为空;
> 2.通过构造注入的依赖的对象被客户端调用时都是完全初始化好的;
> 3.它还可以作为启示:过多的构造参数是一种不好的编程方式,意味着此类拥有过多的职责,最好重构代码,将多余的职责分离开来。
>  
> Setter注入主要用于可选依赖,但是在类中也应该分配合理的默认值,否则在客户端使用此依赖时需要做非空检查。使用Setter注入有一个好处:可以重新配置或者重新注入依赖。通过JMX mbean进行管理是Setter注入的一个引人注目的用例。
> DI只在大多场景下是有效的,当处理没有源码的第三方类时,可以选择其它注入方式。比如:第三方类没有暴露任何Setter方法,那只能通过构造注入了。
*****
 
### **依赖处理过程**
容器处理依赖的的细节如下:
* ` ApplicationContext`通过配置元数据来创建和初始化,配置元数据可以通过XML、Java代码、注解来指定。
* 每个Bean的依赖都表示为:属性、构造参数、静态工厂方法参数。当Bean创建后容器会注入其依赖。
* 每个属性、构造参数都是需要设置的值或者需要引用容器中的其它Bean。
* 每个属性、构造参数的值都被从指定的类型转换成实际类型。默认情况下,Spring可以将字符串类型的值转换为`int`,`long`,`String`,`boolean`等。
Spring容器被创建时会验证每个Bean的配置,然而Bean的属性在Bean没有创建之前不会设置。Bean默认是单例的并且会在容器创建时初始化(关于Bean的作用域将在[1.5.Bean的范围](1.5.Bean的范围.md)详细解释),非单例的Bean将会在被客户请求获取时创建。Bean的创建可能会一系列Bean(Bean的依赖的依赖......的依赖)被创建。注意,这些依赖项之间的不匹配解析可能在较晚的时候才出现,例如在第一次创建受影响的Bean时。
<p style="margin-bottom:0px"> </p>
>### :-: **循环依赖**
>如果通过构造器完成依赖注入,这可能会出现循环依赖的情况。
>
>比如:类A通过构造注入类B的实例,而类B也通过构造注入类A的实例。容器在运行时会检查出循环引用并抛出`BeanCurrentlyInCreationException`。
> 
>一个可行的解决方案:编辑源码将一些类的构造注入替换成Setter注入。此外,还可以避免使用构造注入而只使用Setter注入。换而言之,可以通过Setter注入配置循环依赖,虽然这是不推荐的。
> 
>和典型的场景(没有循环依赖)不同,Bean A和Bean B之间的循环依赖关系迫使在完全初始化之前将其中一个Bean注入到另一个Bean中,这是一个矛盾的问题(这就像一个经典的问题:先有鸡还是先有蛋)。
>
开发者可以信任Spring做的事都是正确的。在容器加载时会检查不存在依赖和循环依赖等问题。当一个Bean被创建时,Spring会尽可能晚的设置属性和解析依赖。这意味着Spring容器被正确的加载后,当你请求一个对象时,如果创建该对象或者它的一项依赖出现问题,生成异常也会更晚。例如:当Bean的属性缺失或者无效时会抛出异常。所以一些配置问题可能延迟可见,这也就是为什么`ApplicationContext`的实现默认提前实例化单例Bean的原因。在容器创建之时花费一些时间和内存创建Bean可以提前发现问题。你仍然可以覆盖提前初始化的行为,将单例Bean的提前加载转变为延迟加载。
 
如果没有循环依赖存在,一个Bean在注入到依赖Bean之前就已经完成初始化好了。这意味着,如果Bean A依赖Bean B,在调用Bean A 的Setter方法之前,Bean B已是完全可用的。换而言之,当一个Bean初始化完成时,它的依赖已经设置好了,相关的生命周期方法(比如:初始化回调方法,详情参见:[1.6.1.生命周期回调函数](1.6.1.生命周期回调函数.md))也调用完毕。
*****
 
### **依赖注入的例子**
下面的样例展示了基于XML配置的Setter注入。
~~~
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 使用ref元素完成Setter注入-->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- 使用更简洁的ref属性完成Setter注入 -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
~~~
~~~
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;
}
}
~~~
在上面的样例中,Setter方法和和`<property/>`对应。下面的样例采用构造注入。
~~~
<bean id="exampleBean" class="examples.ExampleBean">
<!-- 使用ref元素完成Setter注入-->
<constructor-arg>
<ref bean="anotherExampleBean"/>
</constructor-arg>
<!-- 使用更简洁的ref属性完成Setter注入 -->
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg type="int" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
~~~
~~~
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;
}
}
~~~
Bean定义指定的构造器参数会作为ExampleBean构造器中的参数。
现在请参考另一个例子,Spring通过静态工厂来代替构造器创建实例。
~~~
<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"/>
~~~
~~~
public class ExampleBean {
// 私有构造器
private ExampleBean(...) {
...
}
/**
* 静态工厂方法;
* 这个方法的参数可以被认为是待创建Bean的依赖项,
* 不管这些参数实际是如何使用的的。
*/
public static ExampleBean createInstance (AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// 其它操作......
return eb;
}
}
~~~
`<constructor-arg/>`提供给静态工厂方法的参数和构造器实际使用的参数是一致的。从工厂方法返回的对象类型可以和包含静态工厂方法的类不一致,尽管在本例子中是一致的。实例(非静态)工厂方法的用法和静态工厂方法本质上是一样(使用`factory-bean` 代替`class`属性),故不再此详述。
- ---- 阅读说明
- 一、核心技术
- 1.IOC容器
- 1.1.IOC 容器和 Bean 简介
- 1.2.容器概览
- 1.2.1.配置元数据
- 1.2.2.实例化容器
- 1.2.3.使用容器
- 1.3.Bean概览
- 1.3.1.Bean的命名
- 1.3.2.实例化Bean
- 1.4.依赖
- 1.4.1.依赖注入
- 1.4.2.依赖配置详情
- 1.4.5.注入合作者
- 1.4.4.Bean的懒加载
- 1.5.Bean的范围
- 1.6.自定义Bean的特性
- 1.6.1.生命周期回调函数
- 1.7.继承Bean定义
- 1.9.基于注解的容器配置
- 1.9.1.@Required
- 1.12.基于Java的容器配置
- 1.12.3.使用@Bean注解
- 1.15.ApplicationContext的附加功能
- 1.15.4.Web 应用中便捷的 ApplicationContext 实例
- 1.16.BeanFactory
- 2.资源
- 2.7.应用上下文和资源路径
- 3.验证、数据绑定以及类型转换
- 3.5.Spring类型转换
- 3.5.4.ConversionService API
- 5.基于 Spring 的面向切面编程
- 5.8.在 Spring 中使用 AspectJ
- 5.8.1.在 Spring 中使用 AspectJ 注入领域对象
- 二、测试
- 三、数据访问
- 四、Web应用
- 五、Web响应式编程
- 六、集成
- 4.1.介绍
- 七、编程语言