1. 程式人生 > >Spring框架:注入模式詳解

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目錄下,具體如下:

<?xml version="1.0" encoding="UTF-8"?>
<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    
        ">
        <!--配置chinese例項,其實現類是Chinese -->
        <bean id="chinese" class="com.weaver.Chinese">
            <!-- 將stoneAxe注入給axe屬性 -->
            <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..../>標籤中的內容修改如下:

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

      這樣就完成了構造注入。其中<constructor-arg.../>元素指定構造器引數,該引數型別是Axe。Spring會呼叫Chinese類裡帶有Axe引數的構造器來建立chinese例項。

3.兩種注入方式的對比

設定注入的優點如下:

  • 程式開發人員更容易理解,通過setter方法設定依賴關係顯得更加直觀自然。

  • 對於複雜的依賴關係,如果採用構造器注入,會導致構造器過於臃腫,難以閱讀。同時由於Spring在建立Bean例項時,需要同時例項化其依賴的所有例項,會導致效能下降。採用設定注入,則能避免這些問題。

  • 多引數的構造器會顯得更加笨重

構造器的優點如下:

  • 構造注入可以在構造器中決定依賴關係的注入順序,優先依賴的優先注入。
  • 對於依賴關係無需變化的Bean,構造器更有用處。因為沒有setter方法,所有的依賴關係全部都在構造器內設定。也就無需擔心後續程式碼對依賴關係產生破壞。
  • 依賴關係只能在構造器中設定,則只有元件的建立者才能改變元件的依賴關係。對元件的呼叫者而言,元件內部依賴關係完全透明,更符合高內聚的原則。

最後,建議採用以設定注入為主,構造注入為輔的注入策略。