【第三章】 DI 之 3.1 DI的配置使用 ——跟我學spring3

原文來自:見第一章。

3.1.1  依賴和依賴注入

     傳統應用程序設計中所說的依賴一般指“類之間的關係”,那先讓我們複習一下類之間的關係:

     泛化表示類與類之間的繼承關係、接口與接口之間的繼承關係;

       實現表示類對接口的實現

     依賴當類與類之間有使用關係時就屬於依賴關係,不同於關聯關係,依賴不具有“擁有關係”,而是一種“相識關係”,只在某個特定地方(比如某個方法體內)纔有關係。

      關聯表示類與類或類與接口之間的依賴關係,表現爲“擁有關係”;具體到代碼可以用實例變量來表示;

      聚合屬於是關聯的特殊情況,體現部分-整體關係,是一種弱擁有關係;整體和部分可以有不一樣的生命週期;是一種弱關聯;

      組合屬於是關聯的特殊情況,也體現了體現部分-整體關係,是一種強“擁有關係”;整體與部分有相同的生命週期,是一種強關聯;

Spring IoC容器的依賴有兩層含義:Bean依賴容器容器注入Bean的依賴資源。

     Bean依賴容器也就是說Bean要依賴於容器,這裏的依賴是指容器負責創建Bean並管理Bean的生命週期,正是由於由容器來控制創建Bean並注入依賴,也就是控制權被反轉了,這也正是IoC名字的由來,此處的有依賴是指Bean和容器之間的依賴關係

     容器注入Bean的依賴資源容器負責注入Bean的依賴資源,依賴資源可以是Bean、外部文件、常量數據等,在Java中都反映爲對象,並且由容器負責組裝Bean之間的依賴關係,此處的依賴是指Bean之間的依賴關係可以認爲是傳統類與類之間的“關聯”、“聚合”、“組合”關係

爲什麼要應用依賴注入,應用依賴注入能給我們帶來哪些好處呢?

     動態替換Bean依賴對象,程序更靈活替換Bean依賴對象,無需修改源文件:應用依賴注入後,由於可以採用配置文件方式實現,從而能隨時動態的替換Bean的依賴對象,無需修改java源文件;

     更好實踐面向接口編程,代碼更清晰在Bean中只需指定依賴對象的接口,接口定義依賴對象完成的功能,通過容器注入依賴實現;

     更好實踐優先使用對象組合,而不是類繼承因爲IoC容器採用注入依賴,也就是組合對象,從而更好的實踐對象組合。

  • 採用對象組合,Bean的功能可能由幾個依賴Bean的功能組合而成,其Bean本身可能只提供少許功能或根本無任何功能,全部委託給依賴Bean,對象組合具有動態性,能更方便的替換掉依賴Bean,從而改變Bean功能;
  • 而如果採用類繼承,Bean沒有依賴Bean,而是採用繼承方式添加新功能,,而且功能是在編譯時就確定了,不具有動態性,而且採用類繼承導致Bean與子Bean之間高度耦合,難以複用。

     增加Bean可複用性依賴於對象組合,Bean更可複用且複用更簡單;

       降低Bean之間耦合由於我們完全採用面向接口編程,在代碼中沒有直接引用Bean依賴實現,全部引用接口,而且不會出現顯示的創建依賴對象代碼,而且這些依賴是由容器來注入,很容易替換依賴實現類,從而降低Bean與依賴之間耦合;

       代碼結構更清晰要應用依賴注入,代碼結構要按照規約方式進行書寫,從而更好的應用一些最佳實踐,因此代碼結構更清晰。

從以上我們可以看出,其實依賴注入只是一種裝配對象的手段,設計的類結構纔是基礎,如果設計的類結構不支持依賴注入,Spring IoC容器也注入不了任何東西,從而從根本上說如何設計好類結構纔是關鍵,依賴注入只是一種裝配對象手段”。

前邊IoC一章我們已經瞭解了Bean依賴容器,那容器如何注入Bean的依賴資源,Spring IoC容器注入依賴資源主要有以下兩種基本實現方式:

     構造器注入就是容器實例化Bean時注入那些依賴,通過在在Bean定義中指定構造器參數進行注入依賴,包括實例工廠方法參數注入依賴,但靜態工廠方法參數不允許注入依賴;

      setter注入通過setter方法進行注入依賴;

      方法注入能通過配置方式替換掉Bean方法,也就是通過配置改變Bean方法功能。

我們已經知道注入實現方式了,接下來讓我們來看看具體配置吧。

3.1.2  構造器注入

       使用構造器注入通過配置構造器參數實現,構造器參數就是依賴。除了構造器方式,還有靜態工廠、實例工廠方法可以進行構造器注入。如圖3-1所示:

 

圖3-1 實例化

構造器注入可以根據參數索引注入、參數類型注入或Spring3支持的參數名注入,但參數名注入是有限制的,需要使用在編譯程序時打開調試模式(即在編譯時使用“javac –g:vars”在class文件中生成變量調試信息默認是不包含變量調試信息的,從而能獲取參數名字,否則獲取不到參數名字)或在構造器上使用@ConstructorProperties(java.beans.ConstructorProperties)註解來指定參數名。

首先讓我們準備測試構造器類HelloImpl3.java,該類只有一個包含兩個參數的構造器:

  1. package cn.javass.spring.chapter3.helloworld;    
  2. public class HelloImpl3 implements HelloApi {    
  3.     private String message;  
  4. private int index;    
  5. //@java.beans.ConstructorProperties({"message", "index"})    
  6.     public HelloImpl3(String message, int index) {    
  7.         this.message = message;    
  8.         this.index = index;    
  9.     }    
  10.     @Override    
  11.     public void sayHello() {    
  12.         System.out.println(index + ":" + message);    
  13.     }    
  14. }   

一、根據參數索引注入,使用標籤“<constructor-arg index="1" value="1"/>”來指定注入的依賴,其中“index”表示索引,從0開始,即第一個參數索引爲0,“value”來指定注入的常量值,配置方式如下:

二、根據參數類型進行注入,使用標籤“<constructor-arg type="java.lang.String" value="Hello World!"/>”來指定注入的依賴,其中“type”表示需要匹配的參數類型,可以是基本類型也可以是其他類型,如“int”、“java.lang.String”,“value”來指定注入的常量值,配置方式如下:

 

三、根據參數名進行注入,使用標籤“<constructor-arg name="message" value="Hello World!"/>”來指定注入的依賴,其中“name”表示需要匹配的參數名字,“value”來指定注入的常量值配置方式如下:

 

四、讓我們來用具體的例子來看一下構造器注入怎麼使用吧。

(1)首先準備Bean類,在此我們就使用“HelloImpl3”這個類。

(2)有了Bean類,接下來要進行Bean定義配置,我們需要配置三個Bean來完成上述三種依賴注入測試,其中Bean ”byIndex”是通過索引注入依賴;Bean ”byType”是根據類型進行注入依賴;Bean ”byName”是根據參數名字進行注入依賴,具體配置文件(resources/chapter3/ constructorDependencyInject.xml)如下:

  1. <!-- 通過構造器參數索引方式依賴注入 -->    
  2. <bean id="byIndex" class="cn.javass.spring.chapter3.HelloImpl3">    
  3. <constructor-arg index="0" value="Hello World!"/>    
  4.     <constructor-arg index="1" value="1"/>    
  5. </bean>    
  6. <!-- 通過構造器參數類型方式依賴注入 -->    
  7. <bean id="byType" class="cn.javass.spring.chapter3.HelloImpl3">    
  8.    <constructor-arg type="java.lang.String" value="Hello World!"/>    
  9.    <constructor-arg type="int" value="2"/>    
  10. </bean>    
  11. <!-- 通過構造器參數名稱方式依賴注入 -->    
  12. <bean id="byName" class="cn.javass.spring.chapter3.HelloImpl3">    
  13.    <constructor-arg name="message" value="Hello World!"/>    
  14.    <constructor-arg name="index" value="3"/>    
  15. </bean>   

(3)配置完畢後,在測試之前,因爲我們使用了通過構造器參數名字注入方式,請確保編譯時class文件包含“變量信息”,具體查看編譯時是否包含“變量調試信息”請右擊項目,在彈出的對話框選擇屬性;然後在彈出的對話框選擇“Java Compiler”條目,在“Class 文件 生成”框中選擇“添加變量信息到Class文件(調試器使用)”,具體如圖3-2:

 

圖3-2 編譯時打開“添加變量信息選項”

(4)接下來讓我們測試一下配置是否工作,具體測試代碼(cn.javass.spring.chapter3. DependencyInjectTest)如下:

  1. @Test    
  2. public void testConstructorDependencyInjectTest() {          
  3. BeanFactory beanFactory =  new ClassPathXmlApplicationContext("chapter3/constructorDependencyInject.xml");    
  4. //獲取根據參數索引依賴注入的Bean    
  5. HelloApi byIndex = beanFactory.getBean("byIndex", HelloApi.class);    
  6. byIndex.sayHello();    
  7. //獲取根據參數類型依賴注入的Bean    
  8. HelloApi byType = beanFactory.getBean("byType", HelloApi.class);    
  9. byType.sayHello();    
  10. //獲取根據參數名字依賴注入的Bean    
  11. HelloApi byName = beanFactory.getBean("byName", HelloApi.class);    
  12. byName.sayHello();    
  13. }   

       通過以上測試我們已經會基本的構造器注入配置了,在測試通過參數名字注入時,除了可以使用以上方式,還可以通過在構造器上添加@java.beans.ConstructorProperties({"message", "index"})註解來指定參數名字,在HelloImpl3構造器上把註釋掉的“ConstructorProperties”打開就可以了,這個就留給大家做練習,自己配置然後測試一下。

五、大家已經會了構造器注入,那讓我們再看一下靜態工廠方法注入和實例工廠注入吧,其實它們注入配置是完全一樣,在此我們只示範一下靜態工廠注入方式和實例工廠方式配置,測試就留給大家自己練習:

       (1)靜態工廠類

  1. //靜態工廠類    
  2. package cn.javass.spring.chapter3;    
  3. import cn.javass.spring.chapter2.helloworld.HelloApi;    
  4. public class DependencyInjectByStaticFactory {    
  5.        public static HelloApi newInstance(String message, int index) {    
  6.               return new HelloImpl3(message, index);    
  7.        }    
  8. }   

        靜態工廠類Bean定義配置文件(chapter3/staticFactoryDependencyInject.xml)

  1. <bean id="byIndex"    
  2. class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">    
  3. <constructor-arg index="0" value="Hello World!"/>    
  4. <constructor-arg index="1" value="1"/>    
  5. </bean>    
  6. <bean id="byType"    
  7. class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">    
  8. <constructor-arg type="java.lang.String" value="Hello World!"/>    
  9.        <constructor-arg type="int" value="2"/>    
  10. </bean>    
  11. <bean id="byName"    
  12. class="cn.javass.spring.chapter3.DependencyInjectByStaticFactory" factory-method="newInstance">    
  13.        <constructor-arg name="message" value="Hello World!"/>    
  14.        <constructor-arg name="index" value="3"/>    
  15. </bean>   

(2)實例工廠類

  1. //實例工廠類    
  2. package cn.javass.spring.chapter3;    
  3. import cn.javass.spring.chapter2.helloworld.HelloApi;    
  4. public class DependencyInjectByInstanceFactory {    
  5.     public HelloApi newInstance(String message, int index) {    
  6.         return new HelloImpl3(message, index);    
  7.     }    
  8. }   

實例工廠類Bean定義配置文件(chapter3/instanceFactoryDependencyInject.xml)

  1. <bean id="instanceFactory"    
  2. class="cn.javass.spring.chapter3.DependencyInjectByInstanceFactory"/>    
  3.      
  4. <bean id="byIndex"    
  5. factory-bean="instanceFactory"  factory-method="newInstance">    
  6.     <constructor-arg index="0" value="Hello World!"/>    
  7.        <constructor-arg index="1" value="1"/>    
  8. </bean>    
  9. <bean id="byType"    
  10. factory-bean="instanceFactory" factory-method="newInstance">    
  11. <constructor-arg type="java.lang.String" value="Hello World!"/>    
  12. <constructor-arg type="int" value="2"/>    
  13. </bean>    
  14. <bean id="byName"    
  15. factory-bean="instanceFactory" factory-method="newInstance">    
  16. <constructor-arg name="message" value="Hello World!"/>    
  17. <constructor-arg name="index" value="3"/>    
  18. </bean>   

       (3)測試代碼和構造器方式完全一樣,只是配置文件不一樣,大家只需把測試文件改一下就可以了。還有一點需要大家注意就是靜態工廠方式和實例工廠方式根據參數名字注入的方式只支持通過在class文件中添加“變量調試信息”方式才能運行,ConstructorProperties註解方式不能工作,它只對構造器方式起作用,不建議使用根據參數名進行構造器注入

3.1.3  setter注入

       setter注入,是通過在通過構造器、靜態工廠或實例工廠實例好Bean後,通過調用Bean類的setter方法進行注入依賴,如圖3-3所示:

 

圖3-3 setter注入方式

       setter注入方式只有一種根據setter名字進行注入:

 

       知道配置方式了,接下來先讓我們來做個簡單例子吧。

(1)準備測試類HelloImpl4,需要兩個setter方法“setMessage”和“setIndex”:

  1. package cn.javass.spring.chapter3;    
  2. import cn.javass.spring.chapter2.helloworld.HelloApi;    
  3. public class HelloImpl4 implements HelloApi {    
  4.     private String message;    
  5.     private int index;    
  6. //setter方法    
  7.     public void setMessage(String message) {    
  8.         this.message = message;    
  9.     }    
  10.     public void setIndex(int index) {    
  11.         this.index = index;    
  12.     }    
  13.     @Override    
  14.     public void sayHello() {    
  15.         System.out.println(index + ":" + message);    
  16.     }    
  17. }   

  (2)配置Bean定義,具體配置文件(resources/chapter3/setterDependency

 Inject.xml)片段如下:

  1. <!-- 通過setter方式進行依賴注入 -->    
  2.     <bean id="bean" class="cn.javass.spring.chapter3.HelloImpl4">    
  3.         <property name="message" value="Hello World!"/>    
  4.         <property name="index">    
  5.             <value>1</value>    
  6.         </property>    
  7.     </bean>   

(3)該寫測試進行測試一下是否滿足能工作了,其實測試代碼一點沒變,變的是配置:

  1. @Test    
  2. public void testSetterDependencyInject() {    
  3.     BeanFactory beanFactory =    
  4. new ClassPathXmlApplicationContext("chapter3/setterDependencyInject.xml");    
  5.    HelloApi bean = beanFactory.getBean("bean", HelloApi.class);    
  6.     bean.sayHello();    
  7. }   

       知道如何配置了,但Spring如何知道setter方法?如何將值注入進去的呢?其實方法名是要遵守約定的,setter注入的方法名要遵循“JavaBean getter/setter 方法命名約定”:

        JavaBean:是本質就是一個POJO類,但具有一下限制:

      該類必須要有公共的無參構造器,如public HelloImpl4() {};

         屬性爲private訪問級別,不建議public,如private String message;

         屬性必要時通過一組setter(修改器)和getter(訪問器)方法來訪問

         setter方法,以“set” 開頭,後跟首字母大寫的屬性名,如“setMesssage”,簡單屬性一般只有一個方法參數,方法返回值通常爲“void”;

         getter方法,一般屬性以“get”開頭,對於boolean類型一般以“is”開頭,後跟首字母大寫的屬性名,如“getMesssage”,“isOk”;

         還有一些其他特殊情況,比如屬性有連續兩個大寫字母開頭,如“URL”,則setter/getter方法爲:“setURL”和“getURL,其他一些特殊情況請參看“Java Bean”命名規範。

3.1.4  注入常量

       注入常量是依賴注入中最簡單的。配置方式如下所示:

  1. <property name="message" value="Hello World!"/>    
  2. 或    
  3. <property name="index"><value>1</value></property><span class="Apple-style-span" style="font-size: 14px; white-space: normal; background-color: #ffffff;"> </span>   

       以上兩種方式都可以,從配置來看第一種更簡潔。注意此處“value”中指定的全是字符串,由Spring容器將此字符串轉換成屬性所需要的類型,如果轉換出錯,將拋出相應的異常。

Spring容器目前能對各種基本類型把配置的String參數轉換爲需要的類型。

注:Spring類型轉換系統對於boolean類型進行了容錯處理,除了可以使用“true/false”標準的Java值進行注入,還能使用“yes/no”、“on/off”、“1/0”來代表“真/假”,所以大家在學習或工作中遇到這種類似問題不要覺得是人家配置錯了,而是Spring容錯做的非常好。

  1. 測試類    
  2. public class BooleanTestBean {    
  3.     private boolean success;    
  4.     public void setSuccess(boolean success) {    
  5.         this.success = success;    
  6.     }    
  7.     public boolean isSuccess() {    
  8.         return success;    
  9.     }    
  10. }    
  11. 配置文件(chapter3/booleanInject.xml)片段:    
  12. <!-- boolean參數值可以用on/off -->    
  13. <bean id="bean2" class="cn.javass.spring.chapter3.bean.BooleanTestBean">    
  14.     <property name="success" value="on"/>    
  15. </bean>    
  16. <!-- boolean參數值可以用yes/no -->    
  17. <bean id="bean3" class="cn.javass.spring.chapter3.bean.BooleanTestBean">    
  18.     <property name="success" value="yes"/>    
  19. </bean>    
  20. <!-- boolean參數值可以用1/0 -->    
  21. <bean id="bean4" class="cn.javass.spring.chapter3.bean.BooleanTestBean">    
  22.     <property name="success" value="1"/>    
  23. </bean>   

3.1.5    注入Bean ID

用於注入Bean的ID,ID是一個常量不是引用,且類似於注入常量,但提供錯誤驗證功能,配置方式如下所示:

  1. <property name="id"><idref bean="bean1"/></property>    
  2.  
  3. <property name="id"><idref local="bean2"/></property>  

兩種方式都可以,上述配置本質上在運行時等於如下方式

  1. <bean id="bean1" class="……"/>    
  2. <bean id="idrefBean1" class="……">    
  3. <property name="id" value ="bean1"/>    
  4. </bean>   

第一種方式可以在容器初始化時校驗被引用的Bean是否存在,如果不存在將拋出異常,而第二種方式只有在Bean實際使用時才能發現傳入的Bean的ID是否正確,可能發生不可預料的錯誤。因此如果想注入Bean的ID,推薦使用第一種方式。

接下來學習一下如何使用吧:

首先定義測試Bean:

  1. package cn.javass.spring.chapter3.bean    
  2. public class IdRefTestBean {    
  3.     private String id;    
  4.     public String getId() {    
  5.         return id;    
  6.     }    
  7.     public void setId(String id) {    
  8.         this.id = id;    
  9.     }    
  10. }   

其次定義配置文件(chapter3/idRefInject.xml):

  1. <bean id="bean1" class="java.lang.String">    
  2. <constructor-arg index="0" value="test"/>    
  3. </bean>    
  4. <bean id="bean2" class="java.lang.String">    
  5.     <constructor-arg index="0" value="test"/>    
  6. </bean>   
  1. <bean id="idrefBean1" class="cn.javass.spring.chapter3.bean.IdRefTestBean">    
  2.         <property name="id"><idref bean="bean1"/></property>    
  3. </bean>    
  4. <bean id="idrefBean2" class="cn.javass.spring.chapter3.bean.IdRefTestBean">    
  5.     <property name="id"><idref local="bean2"/></property>    
  6. </bean>   

       從配置中可以看出,注入的Bean的ID是一個java.lang.String類型,即字符串類型,因此注入的同樣是常量,只是具有校驗功能。

<idref bean="……"/>將在容器初始化時校驗注入的ID對於的Bean是否存在,如果不存在將拋出異常。

<idref local="……"/>將在XML解析時校驗注入的ID對於的Bean在當前配置文件中是否存在,如果不存在將拋出異常,它不同於<idref bean="……"/>是校驗發生在XML解析式而非容器初始化時,且只檢查當前配置文件中是否存在相應的Bean。

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