Spring的核心机制:依赖注入(Dependency Injection)

来源:互联网 发布:qq刷钻软件2016 编辑:程序博客网 时间:2024/05/22 05:25

本文摘自:李刚 著 《轻量级 Java EE企业应用实战 Struts2+Spring+hibernate整合开发》

 

 

        依赖注入(Dependency Injection) 是时下的"流行语",也是目前最优秀的解耦方式。使用依赖注入时,J2EE中的各种组件不需要以硬编码方式耦合在一起,甚至无须使用工厂模式。当某个Java实例需要其他Java实例时,系统和自动提供所需要的实例,无需程序显式获取。

        依赖注入是Spring 的核心机制,可以使Spring 的bean 以配置文件组织在一起,而不是以硬编码的方式耦合在一起。

 

一. 理解依赖注入

 

        因为某些历史原因,依赖注入还有一种称呼:控制反转(Inversion of Control)。

        不管是依赖注入,还是控制反转,其含义完全相同。当某个Java 实例(调用者)需要另一个Java 实例(被调用者)时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。而在依赖注入的模式下,创建被调用者的工作不再由调用者来完成,通常由Spring 容器来完成,然后注入调用者,因此称为控制反转,也称为依赖注入。

        不管是依赖注入,还是控制反转,都说明Spring 采用动态及灵活的方式来管理各种对象,使对象与对象之间的具体实现互相透明。

        为了更好地理解依赖注入,笔者建议参考人类社会的发展,看如下问题在各种社会形态里如何解决:一个人(Java 实例,调用者)需要一把斧子(Java 实例,被调用者)。

        在"原始社会"里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为:Java 程序里的调用者自己创建被调用者。

        进入"工业社会"后,随着工厂的出现,斧子不再由普通人完成,而在工厂里被生产出来。此时需要斧子的人(调用者)只需找到工厂,购买斧子,无须关心斧子的制造过程。对应简单工厂设计模式:调用者只需要定位工厂,无须管理被调用者具体的实现。

        进入"共产主义"社会后,需要斧子的人甚至无须定位工厂,"坐等"社会提供即可。调用者无须关心被调用者的实现,无须理会工厂,等待Spring 依赖注入即可。

        在第一种情况下,由Java 实例的调用者创建被调用的Java 实例,调用者直接使用new 关键宇创建被调用者实例,其程序高度藕合,效率低下。在实际应用中极少使用这
种方式。

        在第二种情况下,调用者无须关心被调用者的具体实现过程,只需要找到符合某种标准(接口)的实例即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式被大量使用的原因。但调用者需要自己定位工厂,使调用者与工厂耦合在一起。

        第三种情况,是最理想的情况,程序完全无须理会被调用者的实现,也无须定位工厂,是最好的解耦方式。实例之间的依赖关系由容器提供。

        所谓依赖注入,是指在程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。Spring 的依赖注入对调用者和被调用者几乎没有任何要求,完全支持对POJO 之间依赖关系的管理。

        依赖注入方式通常有两种:

        设值注入构造注入

 

二. 设值注入

 

        设值注入是指通过setter 方法传入被调用者的实例。这种注入方式简单、直观,因而在Spring 的依赖注入里大量使用。

        Person接口的代码如下:

package ppp;public interface Person {//Person接口里定义一个使用斧子的方法public void useAxe();}

       Axe接口的代码如下:

package ppp;//定义一个Axe接口public interface Axe {//Axe接口里有一个砍的方法public String chop();}

        Person接口的实现类如下:

package ppp;//Chinese实现Person接口public class Chinese implements Person{//面向Axe接口编程,而不是具体的实现类private Axe axe;public Chinese(){}public setAxe(Axe axe){this.axe = axe;}//实现Person接口的useAxe方法public void useAxe(){System.out.println(axe.chop());}}

        Axe接口的第一个实现类的代码如下:

package ppp;public class StoneAxe implements Axe{//默认构造器public StoneAxe(){}//实现Axe接口的chop方法public String chop(){return "石斧砍柴好慢";}}

        下面采用Spring 的配置文件将Person实例和Axe实例组织在一起。配置文件如下所示:


        从配置文件中可以看到Spring 管理bean 的灵巧性。bean 与bean 之间的依赖关系被放在配置文件里组织,而不是写在代码里。通过配置文件的指定,Spring 能精确地为每
个bean 注入属性。因此,配置文件里bean 的class 元素不能是接口,而必须是真正的实现类。

        另外, Spring 会自动接管每个bean 定义里的property 元素定义。Spring 会在执行无参数的构造器后,创建默认的bean 实例,并调用对应的setter 方法为程序注入属性值。在这里, property 定义的属性值将不再由该bean 来主动创建和管理,而是接收Spring 的注入。

         每个bean 的id 属性是该bean 的唯一标识,程序通过id 属性来访问bean , bean 与bean 的依赖关系也通过id 属性关联。

         以下是主程序部分:

import org.springframework.context.ApplicationContext;import org.springframework.context.support.FileSystemXmlApplicationContext;public class BeanTest {public static void main(String[] args) throws FileNotFoundException, SQLException {    //因为是独立的应用程序,所以显示实例化Spring上下文ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");//通过Person Bean的id来获取bean的实例,面向接口编程,//因此此处强制类型转换为接口类型Person p = (Person)ctx.getBean("chinese");//直接执行Person的useAxe方法p.useAxe();      }}

       程序执行结果是:石斧砍柴好慢

       当主程序调用Person的useAxe()方法时,该方法的方法体内需要使用Axe 的实例,但程序里没有任何地方将特定的Person实例和Axe 实例耦合在一起。或者说程序里没有为Person实例传入Axe的实例,而Axe实例由Spring在运行期间动态注入。Person 实例既不需要了解Axe 实例的具体实现,也无须了解Axe 的创建过程。程序在运行到需要Axe 实例时,由Spring 创建Axe 实例,然后注入给需要Axe 实例的调用者。因此,当Person 实例运行到需要Axe 实例的地方时,自然就产生了Axe 实例,用来供Person 实例使用。

       如果需要改写Axe 的实现类,或者说提供另一个实现类给Person 实例使用时。Person接口、Chinese 类都无须改变,只需提供另一个Axe 的实现类,然后对配置文件进行简单的修改即可。

       Axe 的另一个实现类如下:

package ppp;public class SteelAxe implements Axe{//默认构造器public SteelAxe(){}//实现Axe接口的chop方法public String chop(){return "钢斧砍柴真快";}}

       修改原来的Spring配置文件

        再次运行程序,此时显示:钢斧砍柴真快

        由此可看出, Person 与Axe 之间没有任何代码藕合关系, bean 与bean 之间的依赖关系由Spring 管理。采用以setter 方法为目标bean 注入属性的方式我们称为设值注入。

        通过配置文件动态管理,可使对象与对象之间的依赖关系从代码里分离出来,业务对象的更换也变得相当简单。

 

三. 构造注入

 

        所谓构造注入,指通过构造函数来完成依赖关系的设定,而不是通过setter 方法。对前面代码Chinese 类作简单的修改,修改后的代码如下:

package ppp;//Chinese实现Person接口public class Chinese implements Person{//面向Axe接口编程,而不是具体的实现类private Axe axe;public Chinese(){}//构造注入所需的带参数的构造器public Chinese(Axe axe){this.axe = axe;}         //实现Person接口的useAxe方法public void useAxe(){System.out.println(axe.chop());}}

        此时无须Chinese 类里的setAxe 方法,在构造Person 实例时, Spring 为Person 实例注入所依赖的Axe 实例。

        构造注入的配置文件也需作简单的修改,修改后的配置文件如下:


        由此可看出,执行效果与使用steelAxe 设值注入时的执行效果完全相同。区别在于创建Person 实例中Axe 属性的时机不同一-设值注入是先创建一个默认的bean 实例,然后调用对应的setter方法注入依赖关系;而构造注入则在创建bean 实例时,已经完成了依赖关系的注入。

 

四. 两种注入方式的对比

 

        Spring 同时支持两种依赖注入方式:设值注入构造注入。这两种注入方式各有其优、缺点。

         1   设值注入的优点

        设值注入与传统的JavaBean 的写法更相似,程序开发人员更容易了解,接受。通过setter 方法设定依赖关系显得更加直观、自然。对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。因为Spring 在创建bean 实例时,需要同时实例化其依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题。尤其是在某些属性可选的情况下,多参数的构造器更加笨重。

         2  构造注入的优点

        (1) 可以在构造器中决定依赖关系的注入顺序。例如,组件中其他依赖关系的注入,常常需要依赖于Datasource 的注入。采用构造注入时,可以在代码中清晰地决定注入顺序,优先依赖的优先注入。

        (2)对于依赖关系无须变化的bean ,构造注入更有用处。因为没有setter 方法,所有的依赖关系全部在构造器内设定。因此,无须担心后续的代码对依赖关系产生破坏。

        (3)依赖关系只能在构造器中设定,因为只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。

        建议采用以设值注入为主,构造注入为辅的注入策略。对于依赖关系无须变化的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入。

 

原创粉丝点击