spring06 更多DI知識
一. 延遲初始化
延遲初始化也叫做惰性初始化,指不提前初始化Bean,而是隻有在真正使用時才建立及初始化Bean。
配置方式很簡單隻需在<bean>標籤上指定 “lazy-init” 屬性值為“true”即可延遲初始化Bean
Spring容器會在建立容器時提前初始化“singleton”作用域的Bean,“singleton”就是單例的意思即整個容器 每個Bean只有一個例項,後邊會詳細介紹。Spring容器預先初始化Bean通常能幫助我們提前發現配置錯誤,所以如果 沒有什麼情況建議開啟,除非有某個Bean可能需要載入很大資源,而且很可能在整個應用程式生命週期中很可能使用不 到,可以設定為延遲初始化。
延遲初始化的Bean通常會在第一次使用時被初始化;或者在被非延遲初始化Bean作為依賴物件注入時在會隨著初 始化該Bean時被初始化,因為在這時使用了延遲初始化Bean。
容器管理初始化Bean消除了程式設計實現延遲初始化,完全由容器控制,只需在需要延遲初始化的Bean定義上配置即 可,比程式設計方式更簡單,而且是無侵入程式碼的。
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl" lazy-init="true"/>
二. 使用depends-on
depends-on是指指定Bean初始化及銷燬時的順序,使用depends-on屬性指定的Bean要先初始化完畢後才初始 化當前Bean,由於只有“singleton”Bean能被Spring管理銷燬,所以當指定的Bean都是“singleton”時,使用 depends-on屬性指定的Bean要在指定的Bean之後銷燬.
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> <bean id="decorator" class="cn.javass.spring.chapter3.bean.HelloApiDecorator" depends-on="helloApi"> <property name="helloApi"><ref bean="helloApi"/></property> </bean>
說明:“decorator”指定了“depends-on”屬性為“helloApi”,所以在“decorator”Bean初始化之前要先初始化 “helloApi”,而在銷燬“helloApi”之前先要銷燬“decorator”。
“depends-on”屬性可以指定多個Bean,若指定多個Bean可以用“;”、“,”、空格分割。
那“depends-on”有什麼好處呢?主要是給出明確的初始化及銷燬順序,比如要初始化“decorator”時要確保 “helloApi”Bean的資源準備好了,否則使用“decorator”時會看不到準備的資源;而在銷燬時要先在 “decorator”Bean的把對“helloApi”資源的引用釋放掉才能銷燬“helloApi”,否則可能銷燬 “helloApi”時而 “decorator”還保持著資源訪問,造成資源不能釋放或釋放錯誤。
舉例說明:
,在平常開發中我們可能需要訪問檔案系統,而檔案開啟、關閉是必須配對的,不能開啟後不關 閉,從而造成其他程式不能訪問該檔案。讓我們來看具體配置吧:
1. 準備測試類:
1 public class ResourceBean { 2 private FileOutputStream fos; 3 private File file; 4 //初始化方法 5 public void init() { 6 System.out.println("ResourceBean:========初始化"); 7 //載入資源,在此只是演示 8 System.out.println("ResourceBean:========載入資源,執行一些預操作"); 9 try { 10 this.fos = new FileOutputStream(file); 11 } catch (FileNotFoundException e) { 12 e.printStackTrace(); 13 } 14 } 15 //銷燬資源方法 16 public void destroy() { 17 System.out.println("ResourceBean:========銷燬"); 18 //釋放資源 19 System.out.println("ResourceBean:========釋放資源,執行一些清理操作"); 20 try { 21 fos.close(); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } 25 } 26 public FileOutputStream getFos() { 27 return fos; 28 } 29 public void setFile(File file) { 30 this.file = file; 31 } 32 }View Code
1 public class DependentBean { 2 ResourceBean resourceBean; 3 public void write(String ss) throws IOException { 4 System.out.println("DependentBean:=======寫資源"); 5 resourceBean.getFos().write(ss.getBytes()); 6 } 7 //初始化方法 8 public void init() throws IOException { 9 System.out.println("DependentBean:=======初始化"); 10 resourceBean.getFos().write("DependentBean:=======初始化=====".getBytes()); 11 } 12 //銷燬方法 13 public void destroy() throws IOException { 14 System.out.println("DependentBean:=======銷燬"); 15 //在銷燬之前需要往檔案中寫銷燬內容 16 resourceBean.getFos().write("DependentBean:=======銷燬=====".getBytes()); 17 } 18 19 public void setResourceBean(ResourceBean resourceBean) { 20 this.resourceBean = resourceBean; 21 } 22 }View Code
說明:
ResourceBean從配置檔案中配置檔案位置,然後定義初始化方法init中開啟指定的檔案,然後獲取檔案流;最後定義銷 毀方法destroy用於在應用程式關閉時呼叫該方法關閉掉檔案流。
DependentBean中會注入ResourceBean,並從ResourceBean中獲取檔案流寫入內容;定義初始化方法init用來定義 一些初始化操作並向檔案中輸出檔案頭資訊;最後定義銷燬方法用於在關閉應用程式時想檔案中輸出檔案尾資訊。
2. 準備配置檔案
<bean id="resourceBean" class="cn.javass.spring.chapter3.bean.ResourceBean" init-method="init" destroy-method="destroy"> <property name="file" value="D:/test.txt"/> </bean> <bean id="dependentBean" class="cn.javass.spring.chapter3.bean.DependentBean" init-method="init" destroy-method="destroy" depends-on="resourceBean"> <property name="resourceBean" ref="resourceBean"/> </bean>
<property name="file" value="D:/test.txt"/>配置:Spring容器能自動把字串轉換為java.io.File。
init-method="init" :指定初始化方法,在構造器注入和setter注入完畢後執行。
destroy-method="destroy":指定銷燬方法,只有“singleton”作用域能銷燬,“prototype”作用域的一定不能,其他作用域不一定能;後邊再介紹。
在此配置中,resourceBean初始化在dependentBean之前被初始化,resourceBean銷燬會在dependentBean銷 毀之後執行。
3. 測試
1 public class MoreDependencyInjectTest { 2 @Test 3 public void testDependOn() throws IOException { 4 ClassPathXmlApplicationContext context = 5 new ClassPathXmlApplicationContext("chapter3/depends-on.xml"); 6 //一點要註冊銷燬回撥,否則我們定義的銷燬方法不執行 7 context.registerShutdownHook(); 8 DependentBean dependentBean = 9 context.getBean("dependentBean", DependentBean.class); 10 dependentBean.write("aaa"); 11 } 12 }View Code
測試跟其他測試完全一樣,只是在此我們一定要註冊銷燬方法回撥,否則銷燬方法不會執行。
如果配置沒問題會有如下輸出:
1 ResourceBean:========初始化 2 ResourceBean:========載入資源,執行一些預操作 3 DependentBean:=========初始化 4 DependentBean:=========寫資源 5 DependentBean:=========銷燬 6 ResourceBean:========銷燬 7 ResourceBean:========釋放資源,執行一些清理操作View Code
三. 自動裝配
自動裝配就是指由Spring來自動地注入依賴物件,無需人工參與。
目前Spring3.0支援“no”、“byName ”、“byType”、“constructor”四種自動裝配,預設是“no”指不支 持自動裝配的,其中Spring3.0已不推薦使用之前版本的“autodetect”自動裝配,推薦使用Java 5+支援的 (@Autowired)註解方式代替;如果想支援“autodetect”自動裝配,請將schema改為“spring-beans-2.5.xsd” 或去掉
自動裝配的好處是減少構造器注入和setter注入配置,減少配置檔案的長度。自動裝配通過配置<bean>標籤的 “autowire”屬性來改變自動裝配方式。接下來讓我們挨著看下配置的含義。
表示使用預設的自動裝配,預設的自動裝配需要在<beans>標籤中使用default-autowire屬性指 定,其支援“no”、“byName ”、“byType”、“constructor”四種自動裝配,如果需要覆蓋預設自動裝配,請 繼續往下看;
1. no
意思是不支援自動裝配,必須明確指定依賴。
2. byName
通過設定Bean定義屬性autowire="byName",意思是根據名字進行自動裝配,只能用於setter注 入。比如我們有方法“setHelloApi”,則“byName”方式Spring容器將查詢名字為helloApi的Bean並注入,如果找 不到指定的Bean,將什麼也不注入。
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> <bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator" autowire="byName"/> note:這裡的byName的意思是上面的bean id="helloApi"必須和HelloApiDecorator類中的依賴類HelloImpl的名字一致,private HelloImpl helloApi;
1 public class AutowireBeanTest { 2 @Test 3 public void testAutowireByName() throws IOException { 4 ClassPathXmlApplicationContext context = 5 new ClassPathXmlApplicationContext("chapter3/autowire-byName.xml"); 6 HelloApi helloApi = context.getBean("bean", HelloApi.class); 7 helloApi.sayHello(); 8 } 9 }View Code
是不是不要配置<property>了,如果一個bean有很多setter注入,通過“byName”方式是不是能減少很多 <property>配置。此處注意了,在根據名字注入時,將把當前Bean自己排除在外:比如“hello”Bean類定義了 “setHello”方法,則hello是不能注入到“setHello”的。
3. byType
通過設定Bean定義屬性autowire="byType",意思是指根據型別注入,用於setter注入,比如 如果指定自動裝配方式為“byType”,而“setHelloApi”方法需要注入HelloApi型別資料,則Spring容器將查詢 HelloApi型別資料,如果找到一個則注入該Bean,如果找不到將什麼也不注入,如果找到多個Bean將優先注入 <bean>標籤“primary”屬性為true的Bean,否則丟擲異常來表明有個多個Bean發現但不知道使用哪個。讓我們用例 子來講解一下這幾種情況吧。
在根據型別注入時,將把當前Bean自己排除在外(note:byType時,就不需要helloApi必須和依賴屬性名一樣了,只和型別有關係,因此將上面的例子中byName改成byType就可以直接運行了)
note:
通過設定Bean定義的“autowire-candidate”屬性為false來把指定Bean後自動裝配候選者中移除:
<bean class="cn.javass.spring.chapter2.helloworld.HelloImpl" autowire-candidate="false"/>
4. constructor
:通過設定Bean定義屬性autowire="constructor",功能和“byType”功能一樣,根據類 型注入構造器引數,只是用於構造器注入方式,直接將上面例子的byName改成constructor就好了
5. 全域性自動裝配
可以採用在“<beans>”標籤中通過“default-autowire”屬性指定全域性的自動裝配方式,即如果defaultautowire=”byName”,將對所有Bean進行根據名字進行自動裝配。
6. 不是所有型別都能自動裝配
- 不能自動裝配的資料型別:Object、基本資料型別(Date、CharSequence、Number、URI、URL、Class、int)等;
- 通過“<beans>”標籤default-autowire-candidates屬性指定的匹配模式,不匹配的將不能作為自動裝配的候選者,例如指定“*Service,*Dao”,將只把匹配這些模式的Bean作為候選者,而不匹配的不會作為候選者;
- 通過將“<bean>”標籤的autowire-candidate屬性可被設為false,從而該Bean將不會作為依賴注入的候選者。
7. 陣列、集合、字典型別的根據型別自動裝配和普通型別的自動裝配是有區別的:
- 陣列型別、集合(Set、Collection、List)介面型別:將根據泛型獲取匹配的所有候選者並注入到陣列或集合中,如“List<HelloApi> list”將選擇所有的HelloApi型別Bean並注入到list中,而對於集合的具體型別將只選擇一個候選者,“如 ArrayList<HelloApi> list”將選擇一個型別為ArrayList的Bean注入,而不是選擇所有的HelloApi型別Bean進行注入;
- 字典(Map)介面型別:同樣根據泛型資訊注入,鍵必須為String型別的Bean名字,值根據泛型資訊獲取,如“Map<String, HelloApi> map” 將選擇所有的HelloApi型別Bean並注入到map中,而對於具體字典型別如“HashMap<String, HelloApi> map”將只選擇型別為HashMap的Bean注入,而不是選擇所有的HelloApi型別Bean進行注入。
8. 自動裝配的優缺點
優點:首先,自動裝配確實減少了配置檔案的量;其次, “byType”自動裝配能在相應的Bean更改了欄位型別時自動更新,即修改Bean類不需要修改配置,確實簡單了。
缺點:最重要的缺點就是沒有了配置,在查詢注入錯誤時非常麻煩,還有比如基本型別沒法完成自動裝配,所以可能經常發生一些莫名其妙的錯誤,在此我推薦大家不要使用該方式,最好是指定明確的注入方式,或者採用最新的Java5+註解注入方式。所以大家在使用自動裝配時應該考慮自己負責專案的複雜度來進行衡量是否選擇自動裝配方式。
自動裝配注入方式能和配置注入方式一同工作嗎?當然可以,大家只需記住配置注入的資料會覆蓋自動裝配注入的資料。
9. 依賴檢查
上一節介紹的自動裝配,很可能發生沒有匹配的Bean進行自動裝配,如果此種情況發生,只有在程式執行過程中 發生了空指標異常才能發現錯誤,如果能提前發現該多好啊,這就是依賴檢查的作用。
依賴檢查:用於檢查Bean定義的屬性都注入資料了,不管是自動裝配的還是配置方式注入的都能檢查,如果沒有注入數 據將報錯,從而提前發現注入錯誤,只檢查具有setter方法的屬性。
Spring3+也不推薦配置方式依賴檢查了,建議採用Java5+ @Required註解方式,測試時請將XML schema降低為2.5 版本的,和自動裝配中“autodetect”配置方式的xsd一樣。
依賴檢查有none、simple、object、all四種方式,接下來讓我們詳細介紹一下:
(1)none
預設方式,表示不檢查
(2)objects
:檢查除基本型別外的依賴物件,配置方式為:dependency-check="objects",此處我們為 HelloApiDecorator新增一個String型別屬性“message”,來測試如果有簡單資料型別的屬性為null,也不報錯;
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> <!-- 注意我們沒有注入helloApi,所以測試時會報錯 --> <bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator" dependency-check="objects"> <property name="message" value="Haha"/> </bean>
注意由於我們沒有注入bean需要的依賴“helloApi”,所以應該丟擲異常UnsatisfiedDependencyException,表示沒有發現滿足的依賴:
public class DependencyCheckTest { @Test(expected = UnsatisfiedDependencyException.class) public void testDependencyCheckByObject() throws IOException { //將丟擲異常 new ClassPathXmlApplicationContext("chapter3/dependency-check-object.xml"); } }
(3)simple
對基本型別進行依賴檢查,包括陣列型別,其他依賴不報錯;配置方式為:dependency-check="simple",以下配置中沒有注入message屬性,所以會丟擲異常:
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> <!-- 注意我們沒有注入message屬性,所以測試時會報錯 --> <bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator" dependency-check="simple"> <property name="helloApi" ref="helloApi"/> </bean>
(4)all
對所以型別進行依賴檢查,配置方式為:dependency-check="all",如下配置方式中如果兩個屬性其中一個沒配置將報錯。
<bean id="helloApi" class="cn.javass.spring.chapter2.helloworld.HelloImpl"/> <bean id="bean" class="cn.javass.spring.chapter3.bean.HelloApiDecorator" dependency-check="all"> <property name="helloApi" ref="helloApi"/> <property name="message" value="Haha"/> </bean>
依賴檢查也可以通過“<beans>”標籤中default-dependency-check屬性來指定全域性依賴檢查配置。
參考文獻:
https://jinnianshilongnian.iteye.com/blog/1415461