一、说到依赖注入(控制反转),先要理解什么是依赖。
Spring 把相互协作的关系称为依赖关系。假如 A
组件调用了 B
组件的方法,我们可称A
组件依赖于 B
组件。
二、什么是依赖注入。
在传统的程序设计过程中,通常由调用者来创建被调用者的实例。
在依赖注入的模式下,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者实例的工作通常由Spring
容器来完成,然后注入给调用者,因此也称为依赖注入。
自己理解:即一句话,由spring容器来控制组件A的调用的具体对象B。组件A依赖于spring容器的注入。
三、依赖注入的好处。
不管是依赖注入,还是控制反转,都说明Spring采用动态、灵活的方式来管理各种对象。对象与对象之间的具体实现互相透明。在理解依赖注入之前,看如下这个问题在各种社会形态里如何解决:一个人(Java实例,调用者)需要一把斧子(Java实例,被调用者)。
(1)原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为:Java程序里的调用者自己创建被调用者。
(2)进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。对应Java程序的简单工厂的设计模式。 (此方法依赖于接口)
(3)进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。对应Spring的依赖注入。
第一种情况下,Java实例的调用者创建被调用的Java实例,必然要求被调用的Java类出现在调用者的代码里。无法实现二者之间的松耦合。
第二种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。
情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,系统自动提供被调用者实例。事实上,调用者和被调用者都处于Spring的管理下,二者之间的依赖关系由Spring提供。
延时加载:容器在
依赖注入让 Spring
的 Bean
以被指文件组织在一起,而不是以硬编码的方式耦合在一起。程序完成无须理会被调用者的实现,也不无须主动定位工厂,这是最好的解耦方式。实例之间的依赖关系由
IoC 容器负责管理。
四、依赖注入的 Spring
实现
1、设值注入
设值注入是指 IoC
容器使用属性的 setting
方法来注入被依赖的实例。
先创建一个实体对象(Bean)
[java] view
plaincopy
1. public class HelloWorld {
2. private String msg;
3.
4. public String getMsg() {
5. return msg;
6. }
7. public void setMsg(String msg) {
8. this.msg = msg;
9. }
10. }
再配置文件applicationContext.xml,实例化bean
[java] view
plaincopy
1. <bean id="helloBean" class="com.spring.demo.HelloWorld">
2. <property name="msg" value="Hello World!"/>
3. </bean>
最后测试是否能够得到注入的bean,并打印出对象的属性。
[java] view
plaincopy
1. public static void main(String[] args){
2. //读取配置文件,获得BeanFactory
3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
4. BeanFactory factory = context;
5.
6. HelloWorld hello = (HelloWorld)factory.getBean("hello");
7.
8. System.out.println(hello.getMsg());
9. }
2、构造注入
除了设值注入,还有另一种注入方式,这种方式在构造实例时,已为其完成了依赖关系的初始化。这种利用构造器来设置依赖关系的方式,被称为构造注入。
先创建一个实体对象(Bean)
[java] view
plaincopy
1. public class HelloWorld {
2. private String msg;
3.
4. //需要一个默认无参构造器
5. public HelloWorld(){}
6.
7. public HelloWorld(String msg){
8. this.msg = msg;
9. }
10.
11. public String getMsg() {
12. return msg;
13. }
14. public void setMsg(String msg) {
15. this.msg = msg;
16. }
17. }
再配置文件applicationContext.xml,实例化bean。
[java] view
plaincopy
1. <bean id="hello" class="com.spring.demo.HelloWorld">
2. <constructor-arg index="0">
3. <value>HelloWorld!</value>
4. </constructor-arg>
5. </bean>
最后测试是否能够得到注入的bean,并打印出对象的属性。
[java] view
plaincopy
1. public static void main(String[] args){
2. //读取配置文件,获得BeanFactory
3. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
4. BeanFactory factory = context;
5.
6. HelloWorld hello = (HelloWorld)factory.getBean("hello");
7.
8. System.out.println(hello.getMsg());
9. }
五、处理bean依赖关系的步骤
1、根据定义bean的配置创建并初始化BeanFactory实例
2、每个bean的依赖将以属性、构造器参数、或静态工厂方法参数的形式出现。当这些bean被实际创建时,这些依赖也将会提供给该bean。
3、每个属性或构造器参数既可以是一个实际的值,也可以是对该容器中另一个bean的引用。
4、每个指定的属性或构造器参数值必须能够被转换成特定的格式或构造参数所需的类型。
Spring会在容器被创建时验证容器中每个bean的配置,包括验证那些bean所引用的属性是否指向一个有效的bean。在bean被实际创建之前,bean的属性并不会被设置。伴随着bean被实际创建,作为该bean的依赖bean以及依赖bean的依赖bean也将被创建和分配。
六、两种注入方式的对比
1、相比之下,设值注入具有如下的优点:
(1)、与传统的 JavaBean
的写法更相似,程序开发人员更容易理解、接受。通过 Setting方法设定依赖关系显得更加直观、自然。
(2)、对于复杂的依赖关系,如果采用构造注入,会导致构造过于臃肿,难以阅读。Spring
在创建 Bean
实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。
(3)、尤其是在某些属性可选的情况下,多参数的构造器更加笨重。
2、构造注入也不是绝对不如设值注入,在某些特定的场景下,构造注入比设值注入更优秀。构造注入也有如下优势:
(1)、构造注入可以在构造器中决定依赖关系的注入顺序,有限依赖的优先注入。例如,组件中某些其他依赖关系的注入,尝尝需要依赖于 Datasource
的注入。采用构造注入,可以在代码中清晰地决定注入顺序。
(2)、对于依赖关系无须变化的 Bean
,构造注入更有用处。因为没有 setting
方法,所有的依赖关系全部在构造器内设定。因此,无须担心后续代码对依赖关系产生的破坏。
(3)、依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完成透明,更符合高内聚的原则。
两种方式总结:建议采用以设值注入为住,构造注入为辅的注入策略。对于依赖关系无须变换的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用设值注