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,該類只有一個包含兩個參數的構造器:
?
- package ?cn.javass.spring.chapter3.helloworld;??
- public ? class ?HelloImpl3? implements ?HelloApi?{??
- ???? private ?String?message;??
- private ? int ?index;??
- //@java.beans.ConstructorProperties({"message",?"index"}) ??
- ???? public ?HelloImpl3(String?message,? int ?index)?{??
- ???????? this .message?=?message;??
- ???????? this .index?=?index;??
- ????}??
- ???? @Override ??
- ???? public ? void ?sayHello()?{??
- ????????System.out.println(index?+? ":" ?+?message);??
- ????}??
- }??
?
一、根據參數索引注入,使用標簽“<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)如下:
?
- <!--?通過構造器參數索引方式依賴注入?-->??
- <bean?id= "byIndex" ? class = "cn.javass.spring.chapter3.HelloImpl3" >??
- <constructor-arg?index= "0" ?value= "Hello?World!" />??
- ????<constructor-arg?index= "1" ?value= "1" />??
- </bean>??
- <!--?通過構造器參數類型方式依賴注入?-->??
- <bean?id= "byType" ? class = "cn.javass.spring.chapter3.HelloImpl3" >??
- ???<constructor-arg?type= "java.lang.String" ?value= "Hello?World!" />??
- ???<constructor-arg?type= "int" ?value= "2" />??
- </bean>??
- <!--?通過構造器參數名稱方式依賴注入?-->??
- <bean?id= "byName" ? class = "cn.javass.spring.chapter3.HelloImpl3" >??
- ???<constructor-arg?name= "message" ?value= "Hello?World!" />??
- ???<constructor-arg?name= "index" ?value= "3" />??
- </bean>??
- ???
- ???
?
(3)配置完畢后,在測試之前,因為我們使用了通過構造器參數名字注入方式,請確保編譯時class文件包含“變量信息”,具體查看編譯時是否包含“變量調試信息”請右擊項目,在彈出的對話框選擇屬性;然后在彈出的對話框選擇“Java Compiler”條目,在“Class 文件 生成”框中選擇“添加變量信息到Class文件(調試器使用)”,具體如圖3-2:
?
圖3-2 編譯時打開“添加變量信息選項”
(4)接下來讓我們測試一下配置是否工作,具體測試代碼(cn.javass.spring.chapter3. DependencyInjectTest)如下:
?
- @Test ??
- public ? void ?testConstructorDependencyInjectTest()?{????????
- BeanFactory?beanFactory?=?? new ?ClassPathXmlApplicationContext( "chapter3/constructorDependencyInject.xml" );??
- //獲取根據參數索引依賴注入的Bean ??
- HelloApi?byIndex?=?beanFactory.getBean( "byIndex" ,?HelloApi. class );??
- byIndex.sayHello();??
- //獲取根據參數類型依賴注入的Bean ??
- HelloApi?byType?=?beanFactory.getBean( "byType" ,?HelloApi. class );??
- byType.sayHello();??
- //獲取根據參數名字依賴注入的Bean ??
- HelloApi?byName?=?beanFactory.getBean( "byName" ,?HelloApi. class );??
- byName.sayHello();??
- }??
?
?????? 通過以上測試我們已經會基本的構造器注入配置了,在測試通過參數名字注入時,除了可以使用以上方式,還可以通過在構造器上添加@java.beans.ConstructorProperties({"message", "index"})注解來指定參數名字,在HelloImpl3構造器上把注釋掉的“ConstructorProperties”打開就可以了,這個就留給大家做練習,自己配置然后測試一下。
?
五、大家已經會了構造器注入,那讓我們再看一下靜態工廠方法注入和實例工廠注入吧,其實它們注入配置是完全一樣,在此我們只示范一下靜態工廠注入方式和實例工廠方式配置,測試就留給大家自己練習:
?????? (1)靜態工廠類
? ? ??
- //靜態工廠類 ??
- package ?cn.javass.spring.chapter3;??
- import ?cn.javass.spring.chapter2.helloworld.HelloApi;??
- public ? class ?DependencyInjectByStaticFactory?{??
- ??????? public ? static ?HelloApi?newInstance(String?message,? int ?index)?{??
- ?????????????? return ? new ?HelloImpl3(message,?index);??
- ???????}??
- }??
?
?????? ?靜態工廠類Bean定義配置文件(chapter3/staticFactoryDependencyInject.xml)
?
?
- <bean?id= "byIndex" ??
- class = "cn.javass.spring.chapter3.DependencyInjectByStaticFactory" ?factory-method= "newInstance" >??
- <constructor-arg?index= "0" ?value= "Hello?World!" />??
- <constructor-arg?index= "1" ?value= "1" />??
- </bean>??
- <bean?id= "byType" ??
- class = "cn.javass.spring.chapter3.DependencyInjectByStaticFactory" ?factory-method= "newInstance" >??
- <constructor-arg?type= "java.lang.String" ?value= "Hello?World!" />??
- ???????<constructor-arg?type= "int" ?value= "2" />??
- </bean>??
- <bean?id= "byName" ??
- class = "cn.javass.spring.chapter3.DependencyInjectByStaticFactory" ?factory-method= "newInstance" >??
- ???????<constructor-arg?name= "message" ?value= "Hello?World!" />??
- ???????<constructor-arg?name= "index" ?value= "3" />??
- </bean>??
?
?
(2)實例工廠類
?
- //實例工廠類 ??
- package ?cn.javass.spring.chapter3;??
- import ?cn.javass.spring.chapter2.helloworld.HelloApi;??
- public ? class ?DependencyInjectByInstanceFactory?{??
- ???? public ?HelloApi?newInstance(String?message,? int ?index)?{??
- ???????? return ? new ?HelloImpl3(message,?index);??
- ????}??
- }??
?
? ? ? ? 實例工廠類Bean定義配置文件(chapter3/instanceFactoryDependencyInject.xml)
?
?
- <bean?id= "instanceFactory" ??
- class = "cn.javass.spring.chapter3.DependencyInjectByInstanceFactory" />??
- ???
- <bean?id= "byIndex" ??
- factory-bean= "instanceFactory" ??factory-method= "newInstance" >??
- ????<constructor-arg?index= "0" ?value= "Hello?World!" />??
- ???????<constructor-arg?index= "1" ?value= "1" />??
- </bean>??
- <bean?id= "byType" ??
- factory-bean= "instanceFactory" ?factory-method= "newInstance" >??
- <constructor-arg?type= "java.lang.String" ?value= "Hello?World!" />??
- <constructor-arg?type= "int" ?value= "2" />??
- </bean>??
- <bean?id= "byName" ??
- factory-bean= "instanceFactory" ?factory-method= "newInstance" >??
- <constructor-arg?name= "message" ?value= "Hello?World!" />??
- <constructor-arg?name= "index" ?value= "3" />??
- </bean>??
?
?????? (3)測試代碼和構造器方式完全一樣,只是配置文件不一樣,大家只需把測試文件改一下就可以了。還有一點需要大家注意就是靜態工廠方式和實例工廠方式根據參數名字注入的方式只支持通過在class文件中添加“變量調試信息”方式才能運行,ConstructorProperties注解方式不能工作,它只對構造器方式起作用, 不建議使用根據參數名進行構造器注入 。
?
?
3.1.3? setter注入
?????? setter注入,是通過在通過構造器、靜態工廠或實例工廠實例好Bean后,通過調用Bean類的setter方法進行注入依賴,如圖3-3所示:
?
圖3-3 setter注入方式
?????? setter注入方式只有一種根據setter名字進行注入:
?
?????? 知道配置方式了,接下來先讓我們來做個簡單例子吧。
?
(1)準備測試類HelloImpl4,需要兩個setter方法“setMessage”和“setIndex”:
?
- package ?cn.javass.spring.chapter3;??
- import ?cn.javass.spring.chapter2.helloworld.HelloApi;??
- public ? class ?HelloImpl4? implements ?HelloApi?{??
- ???? private ?String?message;??
- ???? private ? int ?index;??
- //setter方法 ??
- ???? public ? void ?setMessage(String?message)?{??
- ???????? this .message?=?message;??
- ????}??
- ???? public ? void ?setIndex( int ?index)?{??
- ???????? this .index?=?index;??
- ????}??
- ???? @Override ??
- ???? public ? void ?sayHello()?{??
- ????????System.out.println(index?+? ":" ?+?message);??
- ????}??
- }??
?
?????? (2)配置Bean定義,具體配置文件(resources/chapter3/setterDependencyInject.xml)片段如下:
?
- <!--?通過setter方式進行依賴注入?-->??
- ????<bean?id= "bean" ? class = "cn.javass.spring.chapter3.HelloImpl4" >??
- ????????<property?name= "message" ?value= "Hello?World!" />??
- ????????<property?name= "index" >??
- ????????????<value> 1 </value>??
- ????????</property>??
- ????</bean>??
?
? ? ??
(3)該寫測試進行測試一下是否滿足能工作了,其實測試代碼一點沒變,變的是配置:
?
- @Test ??
- public ? void ?testSetterDependencyInject()?{??
- ????BeanFactory?beanFactory?=??
- new ?ClassPathXmlApplicationContext( "chapter3/setterDependencyInject.xml" );??
- ???HelloApi?bean?=?beanFactory.getBean( "bean" ,?HelloApi. class );??
- ????bean.sayHello();??
- }??
??
?????? 知道如何配置了,但Spring如何知道setter方法?如何將值注入進去的呢?其實方法名是要遵守約定的,setter注入的方法名要遵循“JavaBean getter/setter 方法命名約定”:
?
?
?
? ? ? ?JavaBean:是本質就是一個POJO類,但具有一下限制:
? ? ? ? ?該類必須要 有公共的無參構造器 ,如public HelloImpl4() {};
? ? ? ? ? 屬性為private訪問級別 ,不建議public,如private String message;
? ? ? ? ? 屬性必要時通過一組setter(修改器)和getter(訪問器)方法來訪問 ;
? ? ? ? ? setter方法,以“set”?開頭,后跟首字母大寫的屬性名 ,如“setMesssage”,簡單屬性一般只有一個方法參數,方法返回值通常為“void”;
? ? ? ? ?g etter方法,一般屬性以“get”開頭,對于boolean類型一般以“is”開頭,后跟首字母大寫的屬性名 ,如“getMesssage”,“isOk”;
? ? ? ? ?還有一些其他特殊情況, 比如屬性有連續兩個大寫字母開頭,如“URL”,則setter/getter方法為:“setURL”和“getURL” ,其他一些特殊情況請參看“Java Bean”命名規范。
?
?
3.1.4? 注入常量
?????? 注入常量是依賴注入中最簡單的。配置方式如下所示:
?
- <property?name= "message" ?value= "Hello?World!" />??
- 或??
- <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容錯做的非常好。
?
- 測試類??
- public ? class ?BooleanTestBean?{??
- ???? private ? boolean ?success;??
- ???? public ? void ?setSuccess( boolean ?success)?{??
- ???????? this .success?=?success;??
- ????}??
- ???? public ? boolean ?isSuccess()?{??
- ???????? return ?success;??
- ????}??
- }??
- 配置文件(chapter3/booleanInject.xml)片段:??
- <!--? boolean 參數值可以用on/off?-->??
- <bean?id= "bean2" ? class = "cn.javass.spring.chapter3.bean.BooleanTestBean" >??
- ????<property?name= "success" ?value= "on" />??
- </bean>??
- <!--? boolean 參數值可以用yes/no?-->??
- <bean?id= "bean3" ? class = "cn.javass.spring.chapter3.bean.BooleanTestBean" >??
- ????<property?name= "success" ?value= "yes" />??
- </bean>??
- <!--? boolean 參數值可以用 1 / 0 ?-->??
- <bean?id= "bean4" ? class = "cn.javass.spring.chapter3.bean.BooleanTestBean" >??
- ????<property?name= "success" ?value= "1" />??
- </bean>??
3.1.5?? ?注入Bean ID
用于注入Bean的ID,ID是一個常量不是引用,且類似于注入常量,但提供錯誤驗證功能,配置方式如下所示:
?
- <property?name= "id" ><idref?bean= "bean1" /></property>??
?
?
- <property?name= "id" ><idref?local= "bean2" /></property>??
?
兩種方式都可以,上述配置本質上在運行時等于如下方式
?
- <bean?id= "bean1" ? class = "……" />??
- <bean?id= "idrefBean1" ? class = "……" >??
- <property?name= "id" ?value?= "bean1" />??
- </bean>??
?
第一種方式可以在容器初始化時校驗被引用的Bean是否存在,如果不存在將拋出異常,而第二種方式只有在Bean實際使用時才能發現傳入的Bean的ID是否正確,可能發生不可預料的錯誤。因此如果想注入Bean的ID,推薦使用第一種方式。
接下來學習一下如何使用吧:
?
首先定義測試Bean:
?
- package ?cn.javass.spring.chapter3.bean??
- public ? class ?IdRefTestBean?{??
- ???? private ?String?id;??
- ???? public ?String?getId()?{??
- ???????? return ?id;??
- ????}??
- ???? public ? void ?setId(String?id)?{??
- ???????? this .id?=?id;??
- ????}??
- }??
?
其次定義配置文件(chapter3/idRefInject.xml):
?
- <bean?id= "bean1" ? class = "java.lang.String" >??
- <constructor-arg?index= "0" ?value= "test" />??
- </bean>??
- <bean?id= "bean2" ? class = "java.lang.String" >??
- ????<constructor-arg?index= "0" ?value= "test" />??
- </bean>??
?
?
- <bean?id= "idrefBean1" ? class = "cn.javass.spring.chapter3.bean.IdRefTestBean" >??
- ????????<property?name= "id" ><idref?bean= "bean1" /></property>??
- </bean>??
- <bean?id= "idrefBean2" ? class = "cn.javass.spring.chapter3.bean.IdRefTestBean" >??
- ????<property?name= "id" ><idref?local= "bean2" /></property>??
- </bean>??
?
?????? 從配置中可以看出,注入的Bean的ID是一個java.lang.String類型,即字符串類型,因此注入的同樣是常量,只是具有校驗功能。
<idref bean="……"/>將在容器初始化時校驗注入的ID對于的Bean是否存在,如果不存在將拋出異常。
<idref local="……"/>將在XML解析時校驗注入的ID對于的Bean在當前配置文件中是否存在,如果不存在將拋出異常,它不同于<idref bean="……"/>是校驗發生在XML解析式而非容器初始化時,且只檢查當前配置文件中是否存在相應的Bean。
?
3.1.6? 注入集合、數組和字典
?????? Spring不僅能注入簡單類型數據,還能注入集合(Collection、無序集合Set、有序集合List)類型、數組(Array)類型、字典(Map)類型數據、Properties類型數據,接下來就讓我們一個個看看如何注入這些數據類型的數據。
???????一、注入集合類型: 包括Collection類型、Set類型、List類型數據:
???????(1)List類型: 需要使用<list>標簽來配置注入,其具體配置如下:
?
?????? 讓我們來寫個測試來練習一下吧:
? ? ? ? ? ? ?準備測試類:
?
- package ?cn.javass.spring.chapter3.bean;??
- import ?java.util.List;??
- public ? class ?ListTestBean?{??
- ???? private ?List<String>?values;??
- ???? public ?List<String>?getValues()?{??
- ???????? return ?values;??
- ????}??
- ???? public ? void ?setValues(List<String>?values)?{??
- ???????? this .values?=?values;??
- ????}??
- }??
?
??????????????進行Bean定義,在配置文件(resources/chapter3/listInject.xml)中配置list注入:
?
- <bean?id= "listBean" ? class = "cn.javass.spring.chapter3.bean.ListTestBean" >??
- ????<property?name= "values" >??
- ????????<list>??
- ????????????<value> 1 </value>??
- ????????????<value> 2 </value>??
- ????????????<value> 3 </value>??
- ????????</list>??
- ???</property>??
- </bean>??
?
??????????????測試代碼:
?
- @Test ??
- public ? void ?testListInject()?{??
- ???BeanFactory?beanFactory?=??
- new ?ClassPathXmlApplicationContext( "chapter3/listInject.xml" );??
- ListTestBean?listBean?=?beanFactory.getBean( "listBean" ,?ListTestBean. class );??
- System.out.println(listBean.getValues().size());??
- Assert.assertEquals( 3 ,?listBean.getValues().size());??
- }??
?
?
(2)Set類型: 需要使用<set>標簽來配置注入,其配置參數及含義和<lsit>標簽完全一樣,在此就不闡述了:
? ? ? ? ? ? ? 準備測試類:
?
- package ?cn.javass.spring.chapter3.bean;??
- import ?java.util.Collection;??
- public ? class ?CollectionTestBean?{??
- ???? private ?Collection<String>?values;??
- ???? public ? void ?setValues(Collection<String>?values)?{??
- ???????? this .values?=?values;??
- ????}??
- ???? public ?Collection<String>?getValues()?{??
- ???????? return ?values;??
- ????}??
- }??
? ? ? ? ? ??
??????????????進行Bean定義,在配置文件(resources/chapter3/listInject.xml)中配置list注入:
?
- <bean?id= "setBean" ? class = "cn.javass.spring.chapter3.bean.SetTestBean" >??
- <property?name= "values" >??
- <set>??
- <value> 1 </value>??
- <value> 2 </value>??
- <value> 3 </value>??
- </set>??
- </property>??
- </bean>??
? ? ? ? ? ? ? 具體測試代碼就不寫了,和listBean測試代碼完全一樣。
?
??????? (2)Collection類型: 因為Collection類型是Set和List類型的基類型,所以使用<set>或<list>標簽都可以進行注入,配置方式完全和以上配置方式一樣,只是將測試類屬性改成“Collection”類型,如果配置有問題,可參考cn.javass.spring.chapter3.DependencyInjectTest測試類中的testCollectionInject測試方法中的代碼。
二、 注入數組類型 :需要使用<array>標簽來配置注入,其中標簽屬性“value-type”和“merge”和<list>標簽含義完全一樣,具體配置如下:
?
?????? 如果練習時遇到配置問題,可以參考“cn.javass.spring.chapter3.DependencyInjectTest”測試類中的testArrayInject測試方法中的代碼。
?
???????三、注入字典(Map)類型: 字典類型是包含鍵值對數據的數據結構,需要使用<map>標簽來配置注入,其屬性“key-type”和“value-type”分別指定“鍵”和“值”的數據類型,其含義和<list>標簽的“value-type”含義一樣,在此就不羅嗦了,并使用<key>子標簽來指定鍵數據,<value>子標簽來指定鍵對應的值數據,具體配置如下:
?
?????? 如果練習時遇到配置問題,可以參考“cn.javass.spring.chapter3.DependencyInjectTest”測試類中的testMapInject測試方法中的代碼。
四、 Properties注入 :Spring能注入java.util.Properties類型數據,需要使用<props>標簽來配置注入,鍵和值類型必須是String,不能變,子標簽<prop key=”鍵”>值</prop>來指定鍵值對,具體配置如下:
?
?
如果練習時遇到配置問題,可以參考cn.javass.spring.chapter3.DependencyInjectTest測試類中的testPropertiesInject測試方法中的代碼。
?????? 到此我們已經把簡單類型及集合類型介紹完了,大家可能會問怎么沒見注入“Bean之間關系”的例子呢?接下來就讓我們來講解配置Bean之間依賴關系,也就是注入依賴Bean。
?
??
?
3.1.7? 引用其它Bean
?????? 上邊章節已經介紹了注入常量、集合等基本數據類型和集合數據類型,本小節將介紹注入依賴Bean及注入內部Bean。
?????? 引用其他Bean的步驟與注入常量的步驟一樣,可以通過構造器注入及setter注入引用其他Bean,只是引用其他Bean的注入配置稍微變化了一下:可以將“<constructor-arg index="0" value="Hello World!"/>”和“<property name="message" value="Hello World!"/>”中的value屬性替換成bean屬性,其中bean屬性指定配置文件中的其他Bean的id或別名。另一種是把<value>標簽替換為<.ref bean=”beanName”>,bean屬性也是指定配置文件中的其他Bean的id或別名。那讓我們看一下具體配置吧:
?
一、構造器注入方式:
(1)通過” <constructor-arg>”標簽的ref屬性來引用其他Bean,這是最簡化的配置:
?
(2)通過” <constructor-arg>”標簽的子<ref>標簽來引用其他Bean,使用bean屬性來指定引用的Bean:
?
???????二、setter注入方式:
(1)通過” <property>”標簽的ref屬性來引用其他Bean,這是最簡化的配置:
?
(2)通過” <property>”標簽的子<ref>標簽來引用其他Bean,使用bean屬性來指定引用的Bean:
?
?
三、接下來讓我們用個具體例子來講解一下具體使用吧:
(1)首先讓我們定義測試引用Bean的類,在此我們可以使用原有的HelloApi實現,然后再定義一個裝飾器來引用其他Bean,具體裝飾類如下:
?
- package ?cn.javass.spring.chapter3.bean;??
- import ?cn.javass.spring.chapter2.helloworld.HelloApi;??
- public ? class ?HelloApiDecorator? implements ?HelloApi?{??
- private ?HelloApi?helloApi;??
- //空參構造器 ??
- ???? public ?HelloApiDecorator()?{??
- }??
- //有參構造器 ??
- ???? public ?HelloApiDecorator(HelloApi?helloApi)?{??
- ???????? this .helloApi?=?helloApi;??
- }????
- public ? void ?setHelloApi(HelloApi?helloApi)?{??
- ???????? this .helloApi?=?helloApi;??
- ????}??
- ???? @Override ??
- ???? public ? void ?sayHello()?{??
- ????????System.out.println( "==========裝飾一下===========" );??
- ????????helloApi.sayHello();??
- ????????System.out.println( "==========裝飾一下===========" );??
- ????}??
- }??
?
?????? (2)定義好了測試引用Bean接下來該在配置文件(resources/chapter3/beanInject.xml)進行配置Bean定義了,在此將演示通過構造器及setter方法方式注入依賴Bean:
??????
- <!--?定義依賴Bean?-->??
- <bean?id= "helloApi" ? class = "cn.javass.spring.chapter2.helloworld.HelloImpl" />??
- <!--?通過構造器注入?-->??
- <bean?id= "bean1" ? class = "cn.javass.spring.chapter3.bean.HelloApiDecorator" >??
- <constructor-arg?index= "0" ?ref= "helloApi" />??
- </bean>??
- <!--?通過構造器注入?-->??
- <bean?id= "bean2" ? class = "cn.javass.spring.chapter3.bean.HelloApiDecorator" >??
- ????<property?name= "helloApi" ><ref?bean= "?helloApi" /></property>??
- </bean>??
?
?
(3)測試一下吧,測試代碼(cn.javass.spring.chapter3.DependencyInjectTest)片段如下:
?
- @Test ??
- public ? void ?testBeanInject()?{??
- ????BeanFactory?beanFactory?=??
- new ?ClassPathXmlApplicationContext( "chapter3/beanInject.xml" );??
- ???? //通過構造器方式注入 ??
- ????HelloApi?bean1?=?beanFactory.getBean( "bean1" ,?HelloApi. class );??
- ????bean1.sayHello();??
- ???? //通過setter方式注入 ??
- ????HelloApi?bean2?=?beanFactory.getBean( "bean2" ,?HelloApi. class );??
- ????bean2.sayHello();??
- }??
?
???????四、其他引用方式: 除了最基本配置方式以外,Spring還提供了另外兩種更高級的配置方式,<ref local=””/>和<ref parent=””/> :
??????? (1)<ref local=””/>配置方式:用于引用通過<bean id=”beanName”>方式中通過id屬性指定的Bean,它能利用XML解析器的驗證功能在讀取配置文件時來驗證引用的Bean是否存在。因此如果在當前配置文件中有相互引用的Bean可以采用<ref local>方式從而如果配置錯誤能在開發調試時就發現錯誤。
如果引用一個在當前配置文件中不存在的Bean將拋出如下異常:
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line21 inXML document from class path resource [chapter3/beanInject2.xml] is invalid; nested exception is org.xml.sax.SAXParseException: cvc-id.1: There is no ID/IDREF binding for IDREF 'helloApi'.
<ref local>具體配置方式如下:
?
?????? (2)<ref parent=””/>配置方式:用于引用父容器中的Bean,不會引用當前容器中的Bean,當然父容器中的Bean和當前容器的Bean是可以重名的,獲取順序是先查找當前容器中的Bean,如果找不到再從父容器找。具體配置方式如下:
接下來讓我們用個例子演示一下<ref local>和<ref parent>的配置過程:
首先還是準備測試類,在此我們就使用以前寫好的HelloApiDecorator和HelloImpl4類;其次進行Bean定義,其中當前容器bean1引用本地的”helloApi”,而”bean2”將引用父容器的”helloApi”,配置如下:
?
- <!--?sources/chapter3/parentBeanInject.xml表示父容器配置-->??
- <!--注意此處可能子容器也定義一個該Bean-->??
- <bean?id= "helloApi" ? class = "cn.javass.spring.chapter3.HelloImpl4" >??
- <property?name= "index" ?value= "1" />??
- <property?name= "message" ?value= "Hello?Parent!" />??
- </bean>??
?
?
- <!--?sources/chapter3/localBeanInject.xml表示當前容器配置-->??
- <!--?注意父容器中也定義了id?為?helloApi的Bean?-->??
- <bean?id= "helloApi" ? class = "cn.javass.spring.chapter3.HelloImpl4" >??
- <property?name= "index" ?value= "1" />??
- ????<property?name= "message" ?value= "Hello?Local!" />??
- </bean>??
- <!--?通過local注入?-->??
- <bean?id= "bean1" ? class = "cn.javass.spring.chapter3.bean.HelloApiDecorator" >??
- <constructor-arg?index= "0" ><ref?local= "helloApi" /></constructor-arg>??
- </bean>??
- <!--?通過parent注入?-->??
- <bean?id= "bean2" ? class = "cn.javass.spring.chapter3.bean.HelloApiDecorator" >??
- <property?name= "helloApi" ><ref?parent= "helloApi" /></property>??
- </bean>??
?
(3)寫測試類測試一下吧,具體代碼片段如下:?
?
- @Test ??
- public ? void ?testLocalAndparentBeanInject()?{??
- //初始化父容器 ??
- ApplicationContext?parentBeanContext?=??
- new ?ClassPathXmlApplicationContext( "chapter3/parentBeanInject.xml" );??
- //初始化當前容器 ??
- ApplicationContext?beanContext?=? new ?ClassPathXmlApplicationContext(??
- new ?String[]?{ "chapter3/localBeanInject.xml" },?parentBeanContext);??
- ????HelloApi?bean1?=?beanContext.getBean( "bean1" ,?HelloApi. class );??
- ????bean1.sayHello(); //該Bean引用local?bean ??
- HelloApi?bean2?=?beanContext.getBean( "bean2" ,?HelloApi. class );??
- bean2.sayHello(); //該Bean引用parent?bean ??
- }??
?
?“bean1”將輸出“Hello Local!”表示引用當前容器的Bean,”bean2”將輸出“Hello Paren!”,表示引用父容器的Bean,如配置有問題請參考cn.javass.spring.chapter3.DependencyInjectTest中的testLocalAndparentBeanInject測試方法。
?
3.1.8? 內部Bean定義
內部Bean就是在<property>或<constructor-arg>內通過<bean>標簽定義的Bean,該Bean不管是否指定id或name,該Bean都會有唯一的匿名標識符,而且不能指定別名,該內部Bean對其他外部Bean不可見,具體配置如下:
?
(1)讓我們寫個例子測試一下吧,具體配置文件如下:
?
- <bean?id= "bean" ? class = "cn.javass.spring.chapter3.bean.HelloApiDecorator" >??
- <property?name= "helloApi" >??
- <bean?id= "helloApi" ? class = "cn.javass.spring.chapter2.helloworld.HelloImpl" />??
- </property>??
- </bean>??
?
?
?(2)測試代碼(cn.javass.spring.chapter3.DependencyInjectTest.testInnerBeanInject):
?
- @Test ??
- public ? void ?testInnerBeanInject()?{??
- ApplicationContext?context?=??
- new ?ClassPathXmlApplicationContext( "chapter3/innerBeanInject.xml" );??
- HelloApi?bean?=?context.getBean( "bean" ,?HelloApi. class );??
- bean.sayHello();??
- }??
?
3.1.9? 處理null值
?????? ?Spring通過<value>標簽或value屬性注入常量值,所有注入的數據都是字符串,那如何注入null值呢?通過“null”值嗎?當然不是因為如果注入“null”則認為是字符串。Spring通過<null/>標簽注入null值。即可以采用如下配置方式:
?
3.1.10 對象圖導航注入支持
?????? 所謂對象圖導航是指類似a.b.c這種點綴訪問形式的訪問或修改值。Spring支持對象圖導航方式依賴注入。對象圖導航依賴注入有一個限制就是比如a.b.c對象導航圖注入中a和b必須為非null值才能注入c,否則將拋出空指針異常。
?????? Spring不僅支持對象的導航,還支持數組、列表、字典、Properties數據類型的導航,對Set數據類型無法支持,因為無法導航。
數組和列表數據類型可以用array[0]、list[1]導航,注意”[]”里的必須是數字,因為是按照索引進行導航,對于數組類型注意不要數組越界錯誤。
字典Map數據類型可以使用map[1]、map[str]進行導航,其中“[]”里的是基本類型,無法放置引用類型。
?????? 讓我們來練習一下吧。首先準備測試類,在此我們需要三個測試類,以便實現對象圖導航功能演示:
?????? ?NavigationC類用于打印測試代碼,從而觀察配置是否正確;具體類如下所示:
?
- package ?cn.javass.spring.chapter3.bean;??
- public ? class ?NavigationC?{??
- ???? public ? void ?sayNavigation()?{??
- ????????System.out.println( "===navigation?c" );??
- ????}??
- }??
?
?? ?NavigationB類,包含對象和列表、Properties、數組字典數據類型導航,而且這些復合數據類型保存的條目都是對象,正好練習一下如何往復合數據類型中注入對象依賴。具體類如下所示:
?
?
- package ?cn.javass.spring.chapter3.bean;??
- import ?java.util.List;??
- import ?java.util.Map;??
- import ?java.util.Properties;??
- public ? class ?NavigationB?{??
- ???? private ?NavigationC?navigationC;??
- ???? private ?List<NavigationC>?list;??
- ???? private ?Properties?properties;??
- ???? private ?NavigationC[]?array?=? new ?NavigationC[ 1 ];??
- ???? private ?Map<String,?NavigationC>?map;??
- ??? //由于setter和getter方法占用太多空間,故省略,大家自己實現吧 ??
- }??
?
?????? ?NavigationA類是我們的前端類,通過對它的導航進行注入值,具體代碼如下:
?
- package ?cn.javass.spring.chapter3.bean;??
- public ? class ?NavigationA?{??
- ???? private ?NavigationB?navigationB;??
- ???? public ? void ?setNavigationB(NavigationB?navigationB)?{??
- ???????? this .navigationB?=?navigationB;??
- ????}??
- ???? public ?NavigationB?getNavigationB()?{??
- ???????? return ?navigationB;??
- ????}??
- }??
?
?????? ?接下來該進行Bean定義配置(resources/chapter3/navigationBeanInject.xml)了,首先讓我們配置一下需要被導航的數據,NavigationC和NavigationB類,其中配置NavigationB時注意要確保比如array字段不為空值,這就需要或者在代碼中賦值如“NavigationC[] array = new NavigationC[1];”,或者通過配置文件注入如“<list></list>”注入一個不包含條目的列表。具體配置如下:
?
- <bean?id= "c" ? class = "cn.javass.spring.chapter3.bean.NavigationC" />??
- <bean?id= "b" ? class = "cn.javass.spring.chapter3.bean.NavigationB" >??
- <property?name= "list" ><list></list></property>??
- ????<property?name= "map" ><map></map></property>??
- ????<property?name= "properties" ><props></props></property>??
- </bean>??
?
?????? ? 配置完需要被導航的Bean定義了,該來配置NavigationA導航Bean了,在此需要注意,由于“navigationB”屬性為空值,在此需要首先注入“navigationB”值;還有對于數組導航不能越界否則報錯;具體配置如下:
?
- <bean?id= "a" ? class = "cn.javass.spring.chapter3.bean.NavigationA" >??
- <!--?首先注入navigatiionB?確保它非空?-->??
- <property?name= "navigationB" ?ref= "b" />??
- <!--?對象圖導航注入?-->??
- <property?name= "navigationB.navigationC" ?ref= "c" />??
- <!--?注入列表數據類型數據?-->??
- <property?name= "navigationB.list[0]" ?ref= "c" />??
- <!--?注入map類型數據?-->??
- <property?name= "navigationB.map[key]" ?ref= "c" />??
- <!--?注入properties類型數據?-->??
- <property?name= "navigationB.properties[0]" ?ref= "c" />??
- <!--?注入properties類型數據?-->??
- <property?name= "navigationB.properties[1]" ?ref= "c" />??
- <!--?注入數組類型數據?,注意不要越界-->??
- <property?name= "navigationB.array[0]" ?ref= "c" />??
- </bean>??
- ???
?????? ?配置完畢,具體測試代碼在cn.javass.spring.chapter3. DependencyInjectTest,讓我們看下測試代碼吧:
?
- //對象圖導航 ??
- @Test ??
- public ? void ?testNavigationBeanInject()?{??
- ApplicationContext?context?=??
- new ?ClassPathXmlApplicationContext( "chapter3/navigationBeanInject.xml" );?????????
- NavigationA?navigationA?=?context.getBean( "a" ,?NavigationA. class );??
- navigationA.getNavigationB().getNavigationC().sayNavigation();??
- navigationA.getNavigationB().getList().get( 0 ).sayNavigation();??
- navigationA.getNavigationB().getMap().get( "key" ).sayNavigation();??
- navigationA.getNavigationB().getArray()[ 0 ].sayNavigation();??
- ((NavigationC)navigationA.getNavigationB().getProperties().get( "1" ))??
- .sayNavigation();?????????
- }??
- ???
?
?????? 測試完畢,應該輸出5個“===navigation c”,是不是很簡單,注意這種方式是不推薦使用的,了解一下就夠了,最好使用3.1.5一節使用的配置方式。
?????? ??????
3.1.11配置簡寫
讓我們來總結一下依賴注入配置及簡寫形式,其實我們已經在以上部分穿插著進行簡化配置了:
?
一、構造器注入:
1)常量值
簡寫:<constructor-arg index="0" value="常量"/>
全寫:<constructor-arg index="0"><value>常量</value></constructor-arg>
2)引用
簡寫:<constructor-arg index="0" ref="引用"/>
全寫:<constructor-arg index="0"><ref bean="引用"/></constructor-arg>
?
二、setter注入: ??????
?????? 1)常量值
?????? ?簡寫:<property name="message" value="常量"/>
? ? ? ? 全寫:<property name="message"><value>常量</value></ property>
?????? 2)引用
?????? ?簡寫:<property name="message" ref="引用"/>
? ? ? ? 全寫:<property name="message"><ref bean="引用"/></ property>
?????? 3)數組:<array>沒有簡寫形式
?????? 4)列表:<list>沒有簡寫形式
?????? 5)集合:<set>沒有簡寫形式
?????? 6)字典
? ? ? ? ? 簡寫:<map>
???????????? <entry key="鍵常量" value="值常量"/>
???????????? <entry key-ref="鍵引用" value-ref="值引用"/>
??????????? </map>
? ? ? ? ?全寫:<map>
???????????? <entry><key><value>鍵常量</value></key><value>值常量</value></entry>
???????????? <entry><key><ref bean="鍵引用"/></key><ref bean="值引用"/></entry>
?????????? </map>
?????? 7)Properties:沒有簡寫形式
?
三、使用p命名空間簡化setter注入:
?????? 使用p命名空間來簡化setter注入,具體使用如下:
?????????????
?
- <?xml?version= "1.0" ?encoding= "UTF-8" ?>??
- <beans??xmlns= "http://www.springframework.org/schema/beans" ??
- ????????xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" ??
- ????????xmlns:p= "http://www.springframework.org/schema/p" ??
- ????????xsi:schemaLocation="??
- ???????????http: //www.springframework.org/schema/beans ??
- ???????????http: //www.springframework.org/schema/beans/spring-beans-3.0.xsd"> ??
- <bean?id= "bean1" ? class = "java.lang.String" >??
- ????????<constructor-arg?index= "0" ?value= "test" />??
- ????</bean>??
- <bean?id= "idrefBean1" ? class = "cn.javass.spring.chapter3.bean.IdRefTestBean" ??
- p:id= "value" />??
- <bean?id= "idrefBean2" ? class = "cn.javass.spring.chapter3.bean.IdRefTestBean" ??
- p:id-ref= "bean1" />??
- </beans>??
- xmlns:p="http://www.springframework.org/schema/p"?:首先指定p命名空間;
-
<bean id="……" class="……" p:id="value"/>?:
常量setter注入方式,其等價于<property name="id" value="value"/>;
- <bean id="……" class="……" p:id-ref="bean1"/>?: 引用setter注入方式,其等價于<property name="id" ref="bean1"/>。?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
