Spring框架:注入模式详解

刚开始学习Spring框架,把学到的东西拿出来总结和分享一下。

1.什么叫依赖注入?

      在刚开始的时候,Johnson把用配置文件来管理Java实例协作关系的方式叫做控制反转(Inversion of Control),这个名词不是很好理解,于是,后来Martine给起了另外一个名字:依赖注入。
      虽然名字不一样,但是含义是同样的:当某个Java实例(调用者)需要另外一个Java实例(被调用者)时。例如,为了计算1+2的值,我们有两个类,A和B。A类保存1、2的值,B类包含计算1+2的方法add()。我们通常的做法是在A类中创建一个新的B的实例,然后通过这个实例调用B的add()方法获取结果。
      但是在依赖注入的模式下,创建B类的这个过程不需要由调用者A来完成了(在这个意义上便有了控制反转的名字),创建B的工作交给了Spring的容器来完成,然后注入创建者(通过xml的配置完成注入工作),因此也称为依赖注入。

下面通过一个例子来讲解:
      如果一个人需要使用一个斧头。
      在原始社会,他就需要自己制造一个斧头(相当于使用关键词new创建新的对象)。
      在工业社会,他可以找一个斧头工厂,然后从这个工厂里买斧头(相当于Java的工厂设计模式)。
      而在伟大的共产主义社会,他甚至都不用去找工厂,只需要这个社会来提供(注入)给他就行(该模式则为注入模式)。
      在第一种模式下,问题显而易见。可扩展性差,耦合度高:当斧头发生变化时(例如构造方法变化),我们甚至还需要来修改人的代码来完成工作。其次就是人和斧头的职责不清。对于人来说,他只需要使用斧头的功能即可,并不需要关心斧头的创建过程,但是这种模式下,人却需要主动创建“斧头”,多了这么一项“创建斧头”的职责,所以导致了混乱。
      在第二种模式下,虽然可以让人和斧头解耦合,但是带来的问题是,人却和工厂偶尔在一起了,人依然需要主动定位工厂在哪里。
      第三种模式是最理想的,人完全不用理会斧头是怎么实现的,也不用去定位工厂在哪以获得斧头,“社会”会给他提供斧头,他只需要使用斧头的功能就行。而这个“社会”,即确定实例之间的依赖关系的容器,则是LoC容器。

2.依赖注入的类型

依赖注入通常有以下两种:

2.1设值注入

指LoC容器使用属性的setter方法来注入被依赖的实例。下面用一个程序来说明。
Person接口

public interface Person{
      //定义一个使用斧头的方法
      public void useAxe();
}

Axe接口

public interface Axe{
       //Axe有一个砍的方法
       public String chop();
}

接着实现一个石斧的类

public class StoneAxe implements Axe{
        public String chop(){
                return "石斧砍柴好慢"; 
        }
}

实现一个使斧头的Chinese类

public class Chinese implements Persion{
          private Axe axe;
          public void setAxe(Axe axe){
                this.axe = axe;
          }
          public void useAxe(){
                 //调用axe的chop()方法
                 System.out.println(axe.chop());
          }
}

      上面的Chinese的useAxe()方法就是我们说的最典型的依赖关系,即A调用B,B调用C等等。在这里,他需要一个具体的Axe实例,如果想使用这个方法,就需要

Chinese chinese = new Chinese();
chinese.setAxe(new StoneAxe());
chinese.useAxe();

      在Spring框架中,该StoneAxe实例将由Spring容器负责注入,依赖一个bean.xml文件来配置实例间的依赖关系。该文件位于src目录下,具体如下:


<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
        classpath:/org/springframework/beans/factory/xml/spring-beans-4.0.xsd
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.0.xsd    
        ">
        
        <bean id="chinese" class="com.weaver.Chinese">
            
            <property name="axe" ref="stoneAxe"/>   
        bean>
        <bean id="stoneAxe" class="com.weaver.StoneAxe"/>
beans>

      属性的说明如下:
            id:该Bean的唯一标识,程序通过id属性值来访问该Bean实例。
            class:指定该Bean的实现类。Spring容器会使用xml解析器读取该属性值,并利用反射来创建该实现类的实例。

      Spring会在调用无参数的构造器,创建默认的Bean实例后,调用对应的setter方法为程序注入属性值。
在该例中,property 标签中的name 属性对应的就是Chinese类中的axe私有属性。

在下面的主程序代码中,只是简单的获取了Person实例,并调用该实例的useAxe方法。

public class BeanTest{
      public static void main(String[] args) throws Exception{
       //创建Spring容器
       ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
       //获取chinese实例
       Person p = ctx.getBean("chinese", Person.class);
       p.useAxe();
       }
}

执行结果如下:
石斧砍柴好慢

      从上面程序可以看出Spring容器就是一个巨大的工厂,它可以生产出所有类型的Bean实例。
      程序获取Bean实例的方法是getBean()。一旦获取了这个实例后,使用这个实例就没有什么特别之处了。分析程序可以看出,当调用useAxe()方法时,需要一个Axe实例,但是程序没有在任何地方将特定的Person实例和Axe实例耦合在一起, Axe实例由Spring在运行期间注入。Person实例不仅不需要了解Axe实例的具体实现,也不需要了解Axe实例的创建过程。
      假设未来的某一天,我们需要改变Axe的实现,例如将石斧变成钢斧。对应的只需要修改bean.xml就可以完成了。从变化的过程中我们也可以看出,chinese实例与具体的Axe实现类之间没有任何关系,chinese实例仅仅与Axe接口耦合,这就保证了chinese实例与Axe实例间的松耦合——这也是Spring强调面向接口编程的原因。

2.2构造注入

      构造注入在构造实例时,已经为其完成了依赖关系的初始化。这种利用构造器来设置依赖关系的方式,被称为构造注入。

      将前面的chinese类稍微修改如下,增加两个构造器

public class chinese implements Person{
      private Axe axe;
      public chinese(){
      }
      public chinese(Axe axe){
             this.axe = axe;
      }
      public void useAxe(){
              System.out.pringln(axe.chop());
      }
}

      与此同时,将前面的bean.xml文件中的标签中的内容修改如下:

<bean id="chinese" class="com.SpringTest.Chinese">
           <constructor-arg ref="stoneAxe"/>
bean>

      这样就完成了构造注入。其中元素指定构造器参数,该参数类型是Axe。Spring会调用Chinese类里带有Axe参数的构造器来创建chinese实例。

3.两种注入方式的对比

设置注入的优点如下:

  • 程序开发人员更容易理解,通过setter方法设定依赖关系显得更加直观自然。

  • 对于复杂的依赖关系,如果采用构造器注入,会导致构造器过于臃肿,难以阅读。同时由于Spring在创建Bean实例时,需要同时实例化其依赖的所有实例,会导致性能下降。采用设置注入,则能避免这些问题。

  • 多参数的构造器会显得更加笨重

构造器的优点如下:

  • 构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。
  • 对于依赖关系无需变化的Bean,构造器更有用处。因为没有setter方法,所有的依赖关系全部都在构造器内设定。也就无需担心后续代码对依赖关系产生破坏。
  • 依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部依赖关系完全透明,更符合高内聚的原则。

最后,建议采用以设置注入为主,构造注入为辅的注入策略。

你可能感兴趣的