Spring框架:注入模式詳解
剛開始學習Spring框架,把學到的東西拿出來總結和分享一下。
1.什麼叫依賴注入?
在剛開始的時候,Johnson把用配置檔案來管理Java例項協作關係的方式叫做控制反轉(Inversion of Control),這個名詞不是很好理解,於是,後來Martine給起了另外一個名字:依賴注入。
雖然名字不一樣,但是含義是同樣的:當某個Java例項(呼叫者)需要另外一個Java例項(被呼叫者)時。例如,為了計算1+2的值,我們有兩個類,A和B。A類儲存1、2的值,B類包含計算1+2的方法add()。我們通常的做法是在A類中建立一個新的B的例項,然後通過這個例項呼叫B的add()方法獲取結果。
但是在依賴注入的模式下,建立B類的這個過程不需要由呼叫者A來完成了(在這個意義上便有了控制反轉的名字),建立B的工作交給了Spring的容器來完成,然後注入建立者(通過xml的配置完成注入工作),因此也稱為依賴注入。
下面通過一個例子來講解:
如果一個人需要使用一個斧頭。
在原始社會,他就需要自己製造一個斧頭(相當於使用關鍵詞new建立新的物件)。
在工業社會,他可以找一個斧頭工廠,然後從這個工廠裡買斧頭(相當於Java的工廠設計模式)。
而在偉大的共產主義社會,他甚至都不用去找工廠,只需要這個社會來提供(注入)給他就行(該模式則為注入模式)。
在第一種模式下,問題顯而易見。可擴充套件性差,耦合度高:當斧頭髮生變化時(例如構造方法變化),我們甚至還需要來修改人的程式碼來完成工作。其次就是人和斧頭的職責不清。對於人來說,他只需要使用斧頭的功能即可,並不需要關心斧頭的建立過程,但是這種模式下,人卻需要主動建立“斧頭”,多了這麼一項“建立斧頭”的職責,所以導致了混亂。
在第二種模式下,雖然可以讓人和斧頭解耦合,但是帶來的問題是,人卻和工廠偶爾在一起了,人依然需要主動定位工廠在哪裡。
第三種模式是最理想的,人完全不用理會斧頭是怎麼實現的,也不用去定位工廠在哪以獲得斧頭,“社會”會給他提供斧頭,他只需要使用斧頭的功能就行。而這個“社會”,即確定例項之間的依賴關係的容器,則是LoC容器。
2.依賴注入的型別
依賴注入通常有以下兩種:
2.1設值注入
指LoC容器使用屬性的setter方法來注入被依賴的例項。下面用一個程式來說明。
Person介面
public interface Person{
//定義一個使用斧頭的方法
public void useAxe();
}
Axe介面
public interface Axe{
//Axe有一個砍的方法
public String chop();
}
接著實現一個石斧的類
public class StoneAxe implements Axe {
public String chop(){
return "石斧砍柴好慢";
}
}
實現一個使斧頭的Chinese類
public class Chinese implements Persion{
private Axe axe;
public void setAxe(Axe axe){
this.axe = axe;
}
public void useAxe(){
//呼叫axe的chop()方法
System.out.println(axe.chop());
}
}
上面的Chinese的useAxe()方法就是我們說的最典型的依賴關係,即A呼叫B,B呼叫C等等。在這裡,他需要一個具體的Axe例項,如果想使用這個方法,就需要
Chinese chinese = new Chinese();
chinese.setAxe(new StoneAxe());
chinese.useAxe();
在Spring框架中,該StoneAxe例項將由Spring容器負責注入,依賴一個bean.xml檔案來配置例項間的依賴關係。該檔案位於src目錄下,具體如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
classpath:/org/springframework/beans/factory/xml/spring-beans-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
">
<!--配置chinese例項,其實現類是Chinese -->
<bean id="chinese" class="com.weaver.Chinese">
<!-- 將stoneAxe注入給axe屬性 -->
<property name="axe" ref="stoneAxe"/>
</bean>
<bean id="stoneAxe" class="com.weaver.StoneAxe"/>
</beans>
屬性的說明如下:
id:該Bean的唯一標識,程式通過id屬性值來訪問該Bean例項。
class:指定該Bean的實現類。Spring容器會使用xml解析器讀取該屬性值,並利用反射來建立該實現類的例項。
Spring會在呼叫無引數的構造器,建立預設的Bean例項後,呼叫對應的setter方法為程式注入屬性值。
在該例中,property 標籤中的name 屬性對應的就是Chinese類中的axe私有屬性。
在下面的主程式程式碼中,只是簡單的獲取了Person例項,並呼叫該例項的useAxe方法。
public class BeanTest{
public static void main(String[] args) throws Exception{
//建立Spring容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
//獲取chinese例項
Person p = ctx.getBean("chinese", Person.class);
p.useAxe();
}
}
執行結果如下:
石斧砍柴好慢
從上面程式可以看出Spring容器就是一個巨大的工廠,它可以生產出所有型別的Bean例項。
程式獲取Bean例項的方法是getBean()。一旦獲取了這個例項後,使用這個例項就沒有什麼特別之處了。分析程式可以看出,當呼叫useAxe()方法時,需要一個Axe例項,但是程式沒有在任何地方將特定的Person例項和Axe例項耦合在一起, Axe例項由Spring在執行期間注入。Person例項不僅不需要了解Axe例項的具體實現,也不需要了解Axe例項的建立過程。
假設未來的某一天,我們需要改變Axe的實現,例如將石斧變成鋼斧。對應的只需要修改bean.xml就可以完成了。從變化的過程中我們也可以看出,chinese例項與具體的Axe實現類之間沒有任何關係,chinese例項僅僅與Axe介面耦合,這就保證了chinese例項與Axe例項間的鬆耦合——這也是Spring強調面向介面程式設計的原因。
2.2構造注入
構造注入在構造例項時,已經為其完成了依賴關係的初始化。這種利用構造器來設定依賴關係的方式,被稱為構造注入。
將前面的chinese類稍微修改如下,增加兩個構造器
public class chinese implements Person{
private Axe axe;
public chinese(){
}
public chinese(Axe axe){
this.axe = axe;
}
public void useAxe(){
System.out.pringln(axe.chop());
}
}
與此同時,將前面的bean.xml檔案中的<bean..../>
標籤中的內容修改如下:
<bean id="chinese" class="com.SpringTest.Chinese">
<constructor-arg ref="stoneAxe"/>
</bean>
這樣就完成了構造注入。其中<constructor-arg.../>
元素指定構造器引數,該引數型別是Axe。Spring會呼叫Chinese類裡帶有Axe引數的構造器來建立chinese例項。
3.兩種注入方式的對比
設定注入的優點如下:
程式開發人員更容易理解,通過setter方法設定依賴關係顯得更加直觀自然。
對於複雜的依賴關係,如果採用構造器注入,會導致構造器過於臃腫,難以閱讀。同時由於Spring在建立Bean例項時,需要同時例項化其依賴的所有例項,會導致效能下降。採用設定注入,則能避免這些問題。
多引數的構造器會顯得更加笨重
構造器的優點如下:
- 構造注入可以在構造器中決定依賴關係的注入順序,優先依賴的優先注入。
- 對於依賴關係無需變化的Bean,構造器更有用處。因為沒有setter方法,所有的依賴關係全部都在構造器內設定。也就無需擔心後續程式碼對依賴關係產生破壞。
- 依賴關係只能在構造器中設定,則只有元件的建立者才能改變元件的依賴關係。對元件的呼叫者而言,元件內部依賴關係完全透明,更符合高內聚的原則。
最後,建議採用以設定注入為主,構造注入為輔的注入策略。