IOC其實是從我們平常new一個對象的對立面來說的,我們平常使用的對象一般直接使用關鍵字類new一個對象,患處很顯然,使用new那么就表示當前模塊已經不知不覺和new出的對象耦合了,而我們通常都是更高層次的抽象模塊調用底層實現模塊,這樣就產生模塊依賴於具體的實現,這與我們JAVA中提倡的面向接口面向抽象編程是相沖突的,而且這樣做也帶來系統的模塊架構問題。很簡單的例子,在進行數據庫操作的時候,總是業務層調用DAO層,DAO一般采用接口開發,這在一定程度上滿足了松耦合,使業務邏輯層不依賴於具體的DAO。但是我們在使用的時候還是會new一個特定數據庫的DAO,這無形中與特定的數據庫綁定了,雖然我們可以使用抽象工廠模式來獲取DAO 實現類,但除非我們一次性把所有數據庫的DAO寫出來,否則在進行數據庫遷移的時候我們還是得修改DAO工廠類。
那我們使用IOC能達到什么呢?IOC,就是DAO接口的實現不再是業務邏輯層調用工廠類去獲取,而是通過容器(比如spring)來自動的為我們的業務層設置DAO的實現類。這樣整個過程就反過來,以前是我們業務層主動去獲取DAO,而現在是DAO主動被設置到業務邏輯層中來了,這也就是反轉控制的由來。通過IOC,我們就可以在不修改任何代碼的情況下,無縫的實現數據庫的換庫遷移,當然前提還是必須得寫一個實現特定數據庫的DAO。我們把DAO普遍到更多的情況下,那么IOC就為我們帶來更大的方便性,比如一個接口的多個實現,我們只需要配置一下就ok了,而不需要再一個個的寫工廠來來獲取了。這就是IOC為我們帶來的模塊的松耦合和應用的便利性。
那為什么說IOC很簡單呢?說白了其實就是由我們平常的new轉成了使用反射來獲取類的實例,相信任何人只要會用java的反射機制,那么自己寫一個IOC框架也不是不可能的。比如:
……
public Object getInstance(String className) throws Exception
{
Object obj = Class.forName(className).newInstance();
Method[] methods = obj.getClass().getMethods();
for (Method method : methods) {
if (method.getName().intern() == "setString") {
method.invoke(obj, "hello world!");
}
}
}
……
上面的一個方法我們就很簡單的使用了反射為指定的類的setString方法來設置一個hello world!字符串。其實可以看到IOC真的很簡單,當然了IOC簡單並不表示spring的IOC就簡單,spring的IOC的功能強大就在於有一系列非常強大的配置文件維護類,它們可以維護spring配置文件中的各個類的關系,這才是spring的IOC真正強大的地方。在spring的Bean 定義文件中,不僅可以為定義Bean設置屬性,還支持Bean之間的繼承、Bean的抽象和不同的獲取方式等等功能。
在spring的Bean配置中總的來說其實就一個標簽<bean></bean>,這個bean標簽就攘括了幾乎所有的配置,然后bean的繼承、抽象等都是基於此標簽之上的,掌握了bean的配置,詳細可以使自己有一個比較大的提升,尤其是對於新手來說(我也是,呵呵 )。最基礎的bean配置如下:
<bean id="bean_test" class="cn.qtone.test.HelloWorld"></bean>
這里我們就簡單的使用HelloWorld類來實例化,使用默認的構造方法,即相當於我們使用:
HelloWorld tmp = new HelloWorld();
但有一點不同的是在spring配置中的在整個應用期間只有一個實例,即是單例的,當然這個單例是指對一個IOC容器(spring)來說的,而不是我們通常說說的單態模式。當然,spring也可以這樣配置不是單態的實例,比如我們修改如下:
<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype"></bean>
注意其中的不同顏色部分,這樣配置后就表明每次從spring容器中獲取HelloWorld的實例的時候就會new一個新對象,即我們所說的原型,spring中scope默認的是單態(singleton),當然針對web應用程序,還可以配置為request、session等范圍。至於什么時候使用什么權限范圍就要看應用程序的使用了,比如在多線程程序中,單態是否會對程序有所影響就需要考慮了。
如果HelloWorld類沒有空的構造方法,只有如下的兩個構造方法,那我們該如何配置呢?
……
public HelloWorld(String str)
{
……
}
public HelloWorld(Date date, int i)
{
……
}
……
由於沒有默認構造方法,所以我們就需要在bean的配置中寫上構造參數才可以,如下:
<!-- 使用一個參數的構造 -->
<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype">
<constructor-arg><value>hello</value></constructor-arg>
</bean>
上面說的使用一個參數的,即帶字符串參數的構造方法,如果想使用帶日期和整型的構造方法,那么就要做如下的配置了:
<bean id="bean_date" class="java.util.Date" />
<!-- 使用二個參數的構造 -->
<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype">
<constructor-arg ref="bean_date"></constructor-arg>
<constructor-arg><value>345</value></constructor-arg>
</bean>
注意到上面的配置中我們使用了ref關鍵字,這個是表示引用配置文件中的ID為bean_date的對象,另外對於類型,spring會做恰當的轉換,比如將345轉換成數字等。當然,這樣對簡單的構造來說不會有什么問題,如果情況比較復雜的話,那么一般建議使用序號來標定,如下:
<!-- 使用二個參數的構造 -->
<bean id="bean_test" class="cn.qtone.test.HelloWorld" scope="prototype">
<constructor-arg index="0" ref="bean_date"></constructor-arg>
<constructor-arg index="1"><value>345</value></constructor-arg>
</bean>
這樣,使用index屬性表示該參數所在位置了后,無論是spring構造起來,還是我們查看都會很方便。當然,spring也提供了自動查找,也就是依賴查找的功能,但是這個我覺得大家還是少用,因為這樣會使整個配置文件看起來非常的不直觀,而且不清晰,說不定過了一段時間再去看的時候就不知道是什么意思了,在正式應用項目中,還是將各個bean的關系進行組織和編寫清楚為好。
上面所說的都是構造來實例化一個bean,但有時候我們都會使用工廠模式來獲取bean。對於工廠模式,我們一般也使用靜態工廠模式和實例工廠模式,這兩個在spring中配置也是不太一樣的。對於靜態工廠模式來實例化bean的,我們的配置如下:
<bean id="bean_string" class="cn.qtone.test.TestFactory" factory-method="getBean"/>
當然,我們也可以為靜態工廠模式的bean來使用構造參數,這個就不說了。我們上面的bean配置對應的實際代碼就應該是:
……
public static Object getBean()
{
return "hello world";
}
……
那么spring在實例化ID為bean_string的bean時,就會使用TestFactory的getBean()方法來獲取,而且 TestFactory是沒有被實例化的,即是使用靜態方法來獲取的。對於實例工廠模式的話,我們的配置和上面就稍微有點不一樣了,那我們就應該配置兩個 bean, 一個是實例的工廠bean,還一個就是我們要獲取的bean的配置了,如下:
<bean id="bean_factory" class="cn.qtone.test.TestBeanFactory"/>
<bean id="bean_helloWorld" factory-bean="bean_factory" factory-method="getHello"/>
上面的配置中,spring容器將首先實例化一個TestBeanFactory,然后再根據該類的方法getHello來獲取一個bean的實例,我們這里以HelloWorld對象為例,對應的代碼就應該如下:
……
public HelloWorld getHello()
{
return new HelloWorld();
}
……
注意到,我們這里的getHello方法並不是靜態方法,而是實例方法,所以必須先實例化TestBeanFactory后才能夠調用。
上面說的都是如何去實例化一個bean,沒有說到bean的屬性注入。雖然我們也可以通過構造的時候進行一次注入,但這樣做不僅失去了靈活性,而且一長串的構造參數看着也眼疼哈,呵呵。當然,有一種情況下,我們是應該使用構造注入的,就是希望注入的對象不能夠被外界修改時,我們這時候就必須使用構造注入了。對於bean的屬性注入,以HelloWorld為例,我們可以簡單的配置如下:
<bean id="bean_test" class="cn.qtone.test.HelloWorld">
<property name="hello" value="你好!" />
<property name="world" value="世界" />
<property name="date" ref="bean_date" />
</bean>
上面的配置中使用了三個屬性注入,即spring中的setter注入方式。注意第三個屬性,使用了ref,表明這個date屬性的設置參數是關聯到ID 為bean_date的bean上去的。注意在使用setter注入的時候,屬性的名稱不是方法的全名稱,而是滿足javaBean規范的命名方式,即如果屬性名稱為xxx,那么其對應的方法名稱就應該是:setXxx(),注意的是除了xxx中第一個字符不區分大小寫之外,其他的是要嚴格區分的。那么對照上面的配置,我們的HelloWorld的方法就應該如下:
……
public void setHello(String hello)
{
……
}
public void setWorld(String world)
{
……
}
public void setDate(Date date)
{
……
}
……
使用setter注入的好處就是可以很清晰的知道每一個注入的是什么參數和意義,通過名稱就可以很容易的看出來,這也是spring提倡使用setter注入的原因之一。
轉自ITEYE--hehebendan寫的網文