依賴注入—Spring核心機制

Spring貫穿表現層、業務層、持久層。但Spring並不是想取代已有的框架,而是以高度的開放性與他們無縫結合。

Spring的核心容器就是一個超級大工廠,所有的Java對象都被當成Spring容器管理的對象——Spring把容器中的一切對象統稱爲Bean。

Spring使用XML配置文件來管理容器中的Bean,只要是一個Java類並被配置在XML文件中Spring就可以管理他。



加入A組件調用了B組件的方法,我們就可以稱A組件依賴B組件。我們通過使用依賴注入,Java EE應用中的各種組件不需要以硬編碼方式耦合在一起,甚至無需使用工廠模式。當某個Java 實例需要其他Java 實例時,系統自動提供所需要的實例,無需程序顯示獲取,這種自動提供java實例我們謂之爲依賴注入,也可以稱之爲控制反轉(Inversion of Control IoC

 

         其實不管是控制反轉還是依賴注入,他們都可以這樣理解:當某個Java實例(調用者)需要另一個Java實例(被調用者)時,在傳統的程序設計過程中,通常有調用者來創建被調用者的實例。但是在依賴注入/控制反轉模式下,創建被調用者的工作不再是有調用者來完成,而是由spring容器來完成,然後注入調用者。

 

          對於Spring而言,Spring採用動態、靈活的方式來管理各種對象。對象與對象之間的具體實現都是透明的。Spring的依賴注入對調用者和被調用者幾乎沒有任何要求,完全支持對POJO之間依賴關係的管理。

 

          依賴注入通常有如下兩種:

         1、  設置注入:IoC容器使用屬性的setter方法來注入被依賴的實例。

         2、  構造注入:IoC容器使用構造器來注入被依賴的實例。

 

         一、設值注入

        設值注入是指IoC容器使用屬性的setter方法來注入被依賴的實例。這種注入方式比較簡單、直觀。

        下面是Person接口,該接口定義了一個Person規範。使用Spring時無論Dao組件、業務邏輯組件,最好都要先定義其接口,然後再定義實現他的類。(面向接口編程)

[java] view plain copy
  1. public interface Person {  
  2.     //定義使用斧子的方法  
  3.     public void useAxe();  
  4. }  

        Axe接口:

[java] view plain copy
  1. public interface Axe {  
  2.     //Axe接口裏面有個砍的方法  
  3.     public String chop();  
  4. }  


          Person的實現類。

[java] view plain copy
  1. public class Chinese implements Person {  
  2.     private Axe axe;  
  3.     private String name;  
  4.   
  5.     // 設值注入所需的setter方法  
  6.     public void setAxe(Axe axe) {  
  7.         this.axe = axe;  
  8.     }  
  9.   
  10.     public void setName(String name) {  
  11.         this.name = name;  
  12.     }  
  13.   
  14.     // 實現Person接口的userAxe方法  
  15.     public void useAxe() {  
  16.         // 調用axe的chop方法,表明Person對象依賴於Axe對象  
  17.         System.out.println("我是"+name+"用"+axe.chop());  
  18.     }  
  19.   
  20. }  

          上面的代碼實現了Person接口的userAxe()方法,實現該方法時調用了axe的的chop()方法,這就是典型的依賴關係。

在上面的Chinese類中,Chinese類並不知道它要調用的axe實例在哪裏,也不知道axe實例是如何實現的,它只是需要調用一個axe實例,這個Axe實例將由Spring容器負責注入


          Axe的實現類:StoneAxe

[java] view plain copy
  1. public class StoneAxe implements Axe{  
  2.   
  3.     public String chop() {  
  4.         return "石斧砍柴好慢啊!!!";  
  5.     }  
  6.   
  7. }  

         直到這裏,程序依然不知道Chinese類和Axe實例耦合,Spring也不知道!實際上,Spring需要使用XML配置文件來指定實例之間的依賴關係。

        Spring採用了XML文件作爲配置文件。

        對於本應用的XML配置文件如下:

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  5.     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
  6.   
  7.     <!-- 配置Chinese實例,由Spring創建叫chinese的對象(Bean),其實現類是Chinese(必須寫完整類名) -->  
  8.     <bean id="chinese" class="com.spring.service.impl.Chinese"> 
  9.  
  10.         <!-- 將StoneAxe注入給axe屬性,
  11.      即name = "axe"驅動Spring以反射方式執行chinese Bean的setAxe()方法。
  12.       ref="stoneAxe"指定以容器中名爲stoneAxe的Bean作爲setAxe()方法的參數  -->  
  13.         <property name="axe" ref="stoneAxe" />  
  14.  <!-- 與上一個不同之處只是此處注入的不是Bean,而只是一個字符串,所以用value -->  
  15.         <property name="name" value="孫悟空"/>  
  16.     </bean>  
  17.       
  18.       
  19.     <!-- 配置stoneAxe實例,由Spring創建叫stoneAxe的對象(Bean) -->  
  20.     <bean id="stoneAxe" class="com.spring.service.impl.StoneAxe" />  
  21. </beans>  

          在配置文件中,Spring配置Bean實例通常會指定兩個屬性:

          id指定該Bean的唯一標識,程序會通過id屬性值來訪問該Bean實例。

          class指定該Bean的實現類,此處不可再用接口,必須是實現類Spring容器會使用XML解析器讀取該屬性值,並利用反射來創建該實現類的實例。

 

        Spring通過反射機制根據<bean…/>元素穿件一個Java對象,並以<bean…/>中的id屬性值爲其key。Spring根據<property …/>就會以反射機制調用一次setter()方法。 簡單講就是:bean…/>驅動Spring調用構造函數創建對象,<property …/>驅動Spring調用setter()方法,兩者的執行是先後關係,基本沒有間隔。


 

         每個Beanid屬性是該Bean的唯一標識,程序通過id屬性訪問BeanBeanBean的依賴關係也是通過id屬性關聯。

         測試程序:

[java] view plain copy
  1. public class BeanTest {  
  2.     public static void main(String[] args) {  
  3.         //創建Spring容器  
  4.         ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");  
  5.         //獲取Chinese實例  
  6.         Person person = ctx.getBean("chinese",Person.class);  
  7.         person.useAxe();  
  8.     }  
  9.   
  10. }  

         執行上面的程序,執行結果如下:

         主程序調用PersonuserAxe()方法時,該方法的方法體內需要使用Axe實例,但程序沒有任何地方將特定的Person實例和Axe實例耦合在一起,也就是說程序沒有爲Person實例傳入Axe實例,Axe實例有Spring在運行期間注入。

 


       Spring IoC容器有如下3個基本要點:

       1、  應用程序的各個組件面向接口編程。面向接口編程可以將各個組件的耦合提升到接口層次,從而有利於項目後期的擴展。

        2、  應用程序的各組件不再由程序主動產生,而是由Spring容器來負責產生,並初始化。

       3、  Spring採用配置文件、或者Annotation來管理Bean的實現類、依賴關係,Spring容器則根據配置文件,利用反射機制來創建時間,併爲之注入依賴關係。


--------------------------------------------------目前只用到過前一種注入方式----------------------------------------------------------

 


          二、構造注入

         構造注入就是利用構造器來設置依賴關係的方式。

         Japanese類:

[java] view plain copy
  1. public class Japanese implements Person{  
  2.   
  3.     private Axe axe;  
  4.     //默認構造器  
  5.     public Japanese(){  
  6.           
  7.     }  
  8.       
  9.     //構造注入所需的帶參數構造器  
  10.     public Japanese(Axe axe){  
  11.         this.axe = axe;  
  12.     }  
  13.       
  14.     public void useAxe() {  
  15.         System.out.println(axe.chop());  
  16.     }  

 

         上面的Chinese類並沒有setter方法,僅僅只是提供了一個帶Axe屬性的構造器,Spring將通過該構造器爲Chinese注入所依賴的Bean實例。

         構造注入的配置文件需要做一些修改。爲了使用構造注入,使用<constructor-arg…/>元素來指定構造器的參數。如下

[html] view plain copy
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  3.     xmlns="http://www.springframework.org/schema/beans"  
  4.     xsi:schemaLocation="http://www.springframework.org/schema/beans  
  5.     http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">  
  6.   
  7.     <!-- 配置Japanese實例 -->  
  8.     <bean id="japanese" class="com.spring.service.impl.Japanese">  
  9.         <!-- 使用構造注入 ,爲Japanese實例注入SteelAxe實例-->  
  10.         <constructor-arg ref="stoneAxe"/>  
  11.     </bean>  
  12.       
  13.     <!-- 配置stoneAxe實例 -->  
  14.     <bean id="stoneAxe" class="com.spring.service.impl.StoneAxe" />  
  15. </beans>  


         上面的配置文件使用<contructor-arg…/>元素指定了一個構造器參數,該參數類型是Axe,這指定Spring調用Chinese類裏帶一個Axe參數的構造器來創建chinese實例,因爲使用了有參數的構造器創建實例,所以當Bean實例被創建完成後,該Bean的依賴關係也就已經設置完成。

 

        他的執行效果與設值注入的執行效果一樣。但是還是有點卻別:創建Person實例中Axe的屬性時機不同設值注入式先通過無參數的構造器創建一個Bean實例,然後調用它的setter方法注入依賴關係,而構造注入則是直接調用有參數的構造器,當Bean實例創建完成後,依賴關係也已經完成。

 

          三、兩種注入方式的對比

         Spring支持兩種依賴注入方式,這兩種依賴注入方式並沒有好壞之分,只是適合的場景有所不同。

         設值注入有如下優點:

            1、  與傳統的JavaBean的寫法更相似,程序開發人員更加容易理解,接受。通過setter方法設定依賴關係顯得更加直觀、自然。

           2、  對於複雜的依賴關係,如果採用構造注入,會導致構造器過於臃腫,難以閱讀。Spring在創建Bean實例時,需要同時實例化其依賴的全部實例,因此導致性能下降。而設值注入,則可以避免這些問題。

           3、  尤其是在某些屬性可選的情況下,多參數的構造器更加笨重。

       但是構造器也有如下優勢:

           1、  構造注入可以再構造器中決定依賴關係的注入順序,優先依賴的優先注入。

           2、  對於依賴關係無須變化的Bean,構造注入更有用處。因爲沒有setter方法,所有的依賴關係全部在構造器中設定,因此,無須擔心後續的代碼對依賴關係產生破壞。

           3、  依賴關係只能在構造器中設定,則只有組件的創建者才能改變組件的依賴關係。對組件的調用者而言,組件內部的依賴關係完全透明,更加符合高內聚的原則。

         通過上面的對比。所以建議用以設值注入爲主,構造注入爲輔的注入策略。對於依賴關係無須變化的注入,儘量採用構造注入;而其他的依賴關係,則考慮設值注入。

        


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章