依赖注入
什么是依赖注入?为什么要有依赖注入?
典型的企业应用程序不包含单个对象(或Spring的说法中的bean)。即使最简单的应用程序也有几个对象一起工作,将最终的结果展示出来的程序作为一个整体的应用。使用依赖注入将一个个分散开来、没有联系的Bean注入到一起,一起完成工作。
一、依赖注入的解释
依赖注入(DI)是一个过程,通过这种过程,对象可以通过构造函数参数,工厂方法参数或者在构造或返回对象实例后设置的属性来定义它们的依赖关系,
控制反转
容器在创建bean时会注入这些依赖关系。
这个过程从根本上来说是反的,因此名为控制反转(IoC),它本身通过使用类的直接构造或服务定位器模式来控制它自己的依赖关系的实例化或位置。单纯的普通代码与依赖注入(DI)的区别
代码与DI原理相比更加清晰,并且在对象提供依赖关系时解耦更有效。该对象不查找其依赖项,并且不知道依赖项的位置或类。因此,您的类变得更容易测试,特别是当依赖关系位于接口或抽象基类上时,它们允许在单元测试中使用存根或模拟实现。
二、两种主要的DI方式
基于构造器的依赖注入和基于Setter的依赖注入。
2.1 基于构造器的依赖注入
基于构造器的 DI通过容器调用具有多个参数的构造函数完成,每个参数表示一个依赖项。调用static具有特定参数的工厂方法来构造bean几乎是等价的,本讨论static类似地将参数视为构造函数和工厂方法。以下示例显示了只能通过构造函数注入进行依赖注入的类。请注意,这个类没有什么特别之处,它是一个POJO,它不依赖于容器特定的接口,基类或注释。
2.1.1
- 首先创建一个需要注入的类
package org.springframework.test;
/**
* @author:wangdong
* @description:创建一个MovieFinder的bean
*/
public class MovieFinder {
}
- 创建一个被注入的类
package org.springframework.test;
/**
* @author:wangdong
* @description:基于构造器的依赖注入
*/
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
//现在想将这个MovieFinderBean注入到SimpleMovieLister里面去
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
//创建一个SimpleMovieLister的构造器,MovieFinder作为构造参数传入这个构造器
public SimpleMovieLister(MovieFinder movieFinder){
this.movieFinder = movieFinder;
}
}
2.1.2 写一个注入类的例子
- 定义两个类
package org.springframework.test;
/**
* @author:wangdong
* @description:Bar类
*/
public class Bar {
}
package org.springframework.test;
/**
* @author:wangdong
* @description:
*/
public class Baz {
}
- 定义一个Foo类,定义一个构造
package org.springframework.test;
/**
* @author:wangdong
* @description:Foo类
*/
public class Foo {
public Foo(Bar bar,Baz baz){
}
}
在上述中,使用参数的类型产生构造函数参数解析匹配。如果bean定义的构造函数参数中没有可能存在的歧义,那么在bean定义中定义构造函数参数的顺序是当实例化bean时将这些参数提供给相应构造函数的顺序。
没有潜在的歧义存在,假设Bar和Baz类不通过继承相关。因此,以下配置可以正常工作,并且不需要在<constructor-arg/>
元素中明确指定构造函数参数索引和/或类型。
- foo.xml实例化容器的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="foo" class="org.springframework.test.Foo">
<!-- 因为Bar,Baz都是类,且没有冲突和歧义项,所以无需制定参数的类型-->
<constructor-arg index="0" ref="bar"/>
<constructor-arg index="1" ref="baz"/>
</bean>
<bean id="bar" class="org.springframework.test.Bar"></bean>
<bean id="baz" class="org.springframework.test.Baz"></bean>
</beans>
2.1.3写一个构造器注入参数值的例子
- ExampleBean类
package org.springframework.test;
/**
* @author:wangdong
* @description:引入参数不为特定类,需要制定参数的类型
*/
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private int years;
// The Answer to Life, the Universe, and Everything
private String ultimateAnswer;
//这边在实例化Bean的时候需要在.xml文件中指定类型
public ExampleBean(int years, String ultimateAnswer){
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
- exampleBean.xml配置依赖注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 构造函数参数类型匹配 使用type关键字-->
<!-- 如果使用属性明确指定构造函数参数的类型,容器可以使用简单类型的类型匹配type-->
<!--<bean id="exampleBean" class="org.springframework.test.ExampleBean"">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>-->
<!-- 构造函数参数索引-->
<!-- 使用该index属性来明确指定构造函数参数的索引。-->
<!-- 0表示第一个参数,1表示第二个参数 -->
<!-- 除了解决多个简单值的歧义之外,指定索引还解决了构造函数具有相同类型的两个参数的不明确性。请注意,该 索引是基于0的。-->
<!--<bean id="exampleBean" class="org.springframework.test.ExampleBean"">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>-->
<!--构造函数参数名称,您也可以使用构造函数参数名称进行值消歧:-->
<bean id="exampleBean" class="org.springframework.test.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
</beans>
2.1.4 基于Setter的依赖注入
基于Setter的 DI通过调用无参数构造函数或无参数static工厂方法来实例化bean之后,调用bean上的容器调用setter方法来完成。
- 以下示例显示了一个只能使用纯setter注入进行依赖注入的类。这个类是传统的Java。这是一个POJO,它不依赖于容器特定的接口,基类或注释。
package org.springframework.test;
/**
* @author:wangdong
* @description:基于构造器的依赖注入
*/
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
//现在想将这个MovieFinderBean注入到SimpleMovieLister里面去
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder){
this.movieFinder = movieFinder;
}
}
三、ApplicationContext对两种依赖注入的支持
该ApplicationContext容器支持基于构造器的依赖注入和基于Setter方法的依赖注入两种方式。
它也支持一些依赖通过Setter的方式注入,一些已经通过构造器方式注入。
您以a的形式配置依赖关系BeanDefinition,您可以结合PropertyEditor实例将属性从一种格式转换为另一种格式。
大多数Spring用户不直接与这些类(即,编程),而是用XML bean 定义,注释组件(即与注释类@Component, @Controller等等),或@Bean在基于Java的方法@Configuration类。然后将这些源内部转换为实例BeanDefinition并用于加载整个Spring IoC容器实例。
3.1 选择构造器注入还是基于Setter的注入方式
既然你可以混合使用基于构造函数和基于setter的DI,对于可选的依赖项,使用强制依赖和构造方法的构造函数或配置方法是一个很好的经验法则。请注意, 在setter方法上使用@Required注释可用于使属性成为必需的依赖项。
Spring团队通常主张构造器注入,因为它使得人们可以将应用程序组件实现为不可变对象,并确保所需的依赖关系不是null。此外,构造器注入的组件总是以完全初始化的状态返回给客户端(调用)代码。作为一个侧面说明,大量的构造函数参数是一种糟糕的代码异味,这意味着该类可能有太多的责任,应该重构以更好地解决问题的分离问题。
Setter注入主要只应用于可选的依赖关系,这些依赖关系可以在类中分配合理的默认值。否则,必须在代码使用依赖关系的任何地方执行非空检查。setter注入的一个好处是setter方法使得该类的对象可以重新配置或稍后重新注入。通过JMX MBeans进行管理因此是一个引人注目的setter注入用例。
使用对特定类最有意义的DI风格。有时候,在处理没有源代码的第三方类库时,可以灵活选择。例如,如果第三方类不公开任何setter方法,则构造函数注入可能是DI的唯一可用形式。
四、依赖性解决的过程
4.1 该容器执行bean依赖性解析如下
- 使用ApplicationContext描述所有bean的配置元数据创建并初始化该元素。配置元数据可以通过XML,Java代码或注释来指定。
- 对于每个bean,如果使用该属性而不是普通构造函数,则它的依赖关系以属性,构造函数参数或静态工厂方法的参数的形式表示。当bean被实际创建时,这些依赖被提供给bean 。
- 每个属性或构造函数参数都是要设置的值的实际定义,或者是对容器中另一个bean的引用。
- 作为值的每个属性或构造函数参数都从其指定的格式转换为该属性或构造函数参数的实际类型。默认情况下,Spring能够转换成字符串格式提供给所有的内置类型,比如数值int, long,String,boolean,等。
- Spring容器在容器创建时验证每个bean的配置。但是,在实际创建 bean之前,bean属性本身不会被设置。Beans是单身作用域并且被设置为预先实例化的(默认的)是在创建容器时创建的。范围在Bean范围中定义。否则,只有在请求时才创建bean。创建一个bean可能会导致创建一个bean图,因为bean的依赖关系及其依赖关系的依赖关系(等等)被创建和分配。请注意,这些依赖项之间的解决方案不匹配可能会出现晚,即首次创建受影响的bean。
4.2 循环依赖
如果您主要使用构造函数注入,则可能会创建一个无法解析的循环依赖方案。
例如:类A需要通过构造函数注入的类B的实例,而类B需要通过构造函数注入的类A的实例。如果将类A和B的Bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并引发一个 BeanCurrentlyInCreationException。
一种可能的解决方案是编辑某些类的源代码,以便由setter而不是构造器进行配置。或者,避免构造函数注入并仅使用setter注入。换句话说,虽然不推荐,但您可以使用setter注入来配置循环依赖关系。
与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖关系迫使其中一个bean在被完全初始化之前注入另一个bean(经典的鸡/鸡蛋场景)。通常你可以相信Spring框架在做正确的事情。它在容器加载时检测配置问题,例如引用不存在的bean和循环依赖关系。当bean实际创建时,Spring会尽可能晚地设置属性并解决依赖关系。这意味着,如果在创建该对象或其某个依赖关系时遇到问题,那么请求对象时,正确加载的Spring容器可能会稍后生成异常。例如,由于缺少或无效的属性,bean抛出异常。某些配置问题的可能延迟可见性是原因ApplicationContext实现默认预先实例化单例bean。在实际需要这些bean之前,为了创建这些bean需要一定的时间和内存,您ApplicationContext会在创建时发现配置问题,而不是稍后。您仍然可以重写此默认行为,以便单例bean将会进行延迟初始化,而不是预先实例化。
- 如果不存在循环依赖关系,则当一个或多个协作bean被注入到一个依赖bean中时,每个协作bean都被注入到依赖bean 之前完全配置。这意味着如果bean A对bean B有依赖性,Spring IoC容器在调用bean A上的setter方法之前完全配置bean B.换句话说,bean被实例化(如果不是预先实例化的单例),它的将设置依赖关系,并调用相关的生命周期方法
五、依赖注入的例子
5.1下面这个例子是基于Setter依赖注入的,它使用了XML文件最为元数据配置文件。
- ExampleBean
package org.springframework.test.examples;
/**
* @author:wangdong
* @description:基于Setter的依赖注入
*/
public class ExampleBean {
private AnotherBean anotherBean;
private YetAnotherBean yetAnotherBean;
private int integerProperty;
//基于Setter的依赖注入
public void setAnotherBean(AnotherBean anotherBean) {
this.anotherBean = anotherBean;
}
public void setYetAnotherBean(YetAnotherBean yetAnotherBean) {
this.yetAnotherBean = yetAnotherBean;
}
public void setIntegerProperty(int integerProperty) {
this.integerProperty = integerProperty;
}
}
- exampleBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 需要被注入的Bean -->
<bean id="exampleBean" class="org.springframework.test.examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<!-- ref引用下面的两个Bean-->
<property name="anotherBean">
<ref bean="anotherBean"></ref>
</property>
<property name="yetAnotherBean">
<ref bean="yetAnotherBean"></ref>
</property>
<!-- 引入非Bean的参数,需要给定一个值,例如1-->
<property name="integerProperty" value="1"></property>
</bean>
<!-- 下面的两个Bean是要注入到ExampleBean中的-->
<!-- 引入的非Bean的参数就不用在这边写出来-->
<bean id="anotherBean" class="org.springframework.test.examples.AnotherBean"></bean>
<bean id="yetAnotherBean" class="org.springframework.test.examples.YetAnotherBean"></bean>
</beans>
5.2 下面的例子是基于构造器注入,.xml文件配置元数据数据
- ExampleBean
package org.springframework.test.examples.constructor;
/**
* @author:wangdong
* @description:基于构造器的依赖注入
*/
public class ExampleBean {
private AnotherBean anotherBean;
private YetAnotherBean yetAnotherBean;
private int integerProperty;
//基于构造器的依赖注入
public ExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int integerProperty){
this.anotherBean = anotherBean;
this.yetAnotherBean = yetAnotherBean;
this.integerProperty = integerProperty;
}
}
- ExampleBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="exampleBean" class="org.springframework.test.examples.constructor.ExampleBean">
<!--引入Bean -->
<constructor-arg>
<ref bean="anotherBean"></ref>
</constructor-arg>
<constructor-arg>
<ref bean="yetAnotherBean"></ref>
</constructor-arg>
<!--引入参数-->
<constructor-arg type="int" value="1"></constructor-arg>
</bean>
<!-- 引入Bean-->
<bean id="anotherBean" class="org.springframework.test.examples.constructor.AnotherBean"></bean>
<bean id="yetAnotherBean" class="org.springframework.test.examples.constructor.YetAnotherBean"></bean>
</beans>
六、下面来考虑用一个Static静态工厂
静态工厂的工厂方法来返回一个对象的例子(严格意义上不属于依赖注入的两种方式)。
- StaticExampleBean
package org.springframework.test.examples.staticexample;
/**
* @author:wangdong
* @description:静态工厂的工厂方法返回一个实例
*/
public class StaticExampleBean {
//一个私有的有参构造
//下面会引用它
private StaticExampleBean(AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i){
}
// 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.
//一个静态工厂方法,他与依赖注入的差别是他可以不返回一个StaticExampleBean的Bean
//StaticExampleBean在这里是指返回的数据的类型,现在是StaticExampleBean
//参数anotherBean、yetAnotherBean和i,都不要直接在类中声明出来
public static StaticExampleBean createInstance(AnotherBean anotherBean, YetAnotherBean yetAnotherBean,int i){
StaticExampleBean staticExampleBean = new StaticExampleBean(anotherBean,yetAnotherBean,i);
return staticExampleBean;
}
}
- StaticExampleBean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="staticExampleBean" class="org.springframework.test.examples.staticexample.StaticExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherBean"></constructor-arg>
<constructor-arg ref="yetAnotherBean"></constructor-arg>
<constructor-arg value="1"></constructor-arg>
</bean>
<bean id="anotherBean" class="org.springframework.test.examples.staticexample.AnotherBean"></bean>
<bean id="yetAnotherBean" class="org.springframework.test.examples.staticexample.YetAnotherBean"></bean>
</beans>
好啦,结束啦,感谢大家的支持。