1. 程式人生 > >2.偏頭痛楊的spring教學系列之依賴注入篇

2.偏頭痛楊的spring教學系列之依賴注入篇

前戲

控制反轉與依賴注入是spring中最最最重要的概念,沒有之一, 常常拿出來一起說,那麼什麼是控制?又怎樣去反轉? 什麼是依賴?又怎樣去注入? 今天讓我們來揭開依賴注入&控制反轉的神祕面紗。 本文的程式碼全部使用xml的方式,註解方式後面會單獨有一章去講解。

什麼是依賴注入&控制反轉

首先讓我們弄明白一個概念:依賴。 其實很簡單,當a要呼叫b時候,那麼我們稱為a依賴於b。

在沒有spring之前,如果我們想在a類去呼叫b的物件, 我們都是在a類中去new一個b物件出來,再使用,這樣造成了一個緊耦合, 為什麼說是緊耦合呢?以後我們如果要去修改這個依賴關係,怎麼辦呢? 之前是a依賴b,現在我們想主觀的改變成a依賴c,那我們就得去修改a類的程式碼,

沒辦法,因為我們是硬編碼式的耦合,這就是所謂的緊耦合&硬編碼耦合, 每次修改依賴關係都需要去修改程式碼,實為下策。

有的同學可能會說,那我們還可以使用工廠模式呀,沒錯,但是否還有更好的方法嗎? 能不能讓所謂的系統自動來提供物件呢?

我們可以把依賴關係都儲存在spring容器中, 這個new的步驟可以讓容器來做,可以通過配置檔案&註解告訴容器需要怎麼去做。

容器把相關的例項都new好了存在容器中,然後誰要就給誰用, 誰要就給誰注入。站在使用者的角度,一切都是那麼的自然,那麼的和諧,那種無所畏懼的眼神。。。不用再像以前一樣,自己去各種new了, 現在全部由spring容器來幫我打理。

這樣我們就不再需要硬編碼耦合了,換句話說我們不再需要自己new物件了。

讓容器把依賴關係注入到相關的業務bean中, 之前是a來控制依賴關係,現在反轉給了容器去控制,這就是控制反轉IOC。 容器把物件通過注入的方式提供給業務bean,這就是依賴注入DI。

不管是依賴注入,還是控制反轉,都說明spring採用動態, 靈活的方式來管理各種物件。物件與物件之間的具體實現互相透明。

一個小問題: 隨著時代的發展與進步,spring由最開始的xml方法配置依賴關係, 轉到了使用註解配置依賴關係,有些同學會問了,那這不相當於時代在倒退嗎?

眾所周知,使用xml來配置依賴關係的特點就是靈活, 如果依賴關係發生改變,只需要修改xml即可,並不需要去修改程式碼, 不需要重新編譯程式碼,只需要重啟服務即可完成, 但註解的話,就需要重新編譯程式碼了,依賴關係仍然硬耦合在程式碼裡。

但其實xml也有其缺點,就是太複雜,讓開發者寫一堆標籤特別傻, 效率低,並且這些標籤大多數是亙古不變的, 那基於這種情況倒不如使用更為快捷的註解。

個人建議: 對於經常需要變化的內容使用xml,否則使用註解。

引用一個網上的話術,加深理解依賴注入&控制反轉

一個人(java例項,呼叫者)需要一把斧子(java例項,被呼叫者)。

在原始社會裡,幾乎沒有社會分工,需要斧子的人(呼叫者)只能自己去磨一把斧子(被呼叫者)。 java程式裡的呼叫者自己建立被呼叫者,通常採用new關鍵字呼叫構造器建立一個被呼叫者,這是java初學者經常乾的事情。程式高度耦合,效率低下。

進入工業社會,工廠出現了,斧子不再由普通人完成,而在工廠裡被生產出來,此時需要斧子的人(呼叫者)找到工廠,購買斧子,無需關心斧子的製造過程,只需要找到符合某種標準(介面)的例項,此時呼叫的程式碼面向介面程式設計,可以讓呼叫者和被呼叫者解耦。對應簡單工廠設計模式,呼叫者只需要定位工廠,無需管理被呼叫者具體的實現。但是呼叫者依然需要主動定位工廠,呼叫者與工廠耦合在一起。

進入共產主義社會,需要斧子的人甚至無需定位工廠,坐等社會提供即可。呼叫者無需關心被呼叫者的實現,無需理會工廠,等待spring依賴注入。例項之間的依賴關係由ioc容器來管理。

所謂依賴注入,是指程式執行過程中,如果需要另一個物件協作(呼叫它的方法,訪問它的屬性)時,無需在程式碼中建立被呼叫者,而是依賴於外部容器的注入。spring的依賴注入對呼叫者和被呼叫者幾乎沒有任何要求,完全支援對pojo之間的依賴管理。

依賴注入的方式

依賴注入有兩種方式:設值注入與構造注入。 使用依賴注入,不僅可以為bean注入普通的屬性值, 還可以注入其他bean的引用。

1.設值注入(setter注入)

<bean id="chinese" class="xx.Chinese">
  <property name="axe" ref="stoneAxe"/>
</bean>

<bean id="stoneAxe" class="xxx.StoneAxe "/>

id:指定該Bean的唯一標識,程式通過id屬性值來訪問該bean例項。 class:指定該bean的實現類,此處不可再用介面,必須使用實現類, spring容器會使用xml解析器讀取該屬性值, 並利用反射來建立該實現類的例項。

spring會自動接管每個定義裡的元素定義, spring會在呼叫無引數的構造器後、建立預設的bean例項後, 呼叫對應的setter方法為程式注入屬性值。

定義的屬性值將不再由該bean來主動設定,管理, 而是接收spring的注入。

每個bean的id屬性是該bean的唯一標識, 程式通過id屬性訪問bean,bean與bean的依賴關係也通過id屬性關聯。

設值注入的原理

spring容器根據xml配置,利用反射來建立例項,併為之注入依賴關係。

<bean id="id" class="xx.AClass">
  <property name="aaa" ref="ava1"/>
  <property name="bbb" ref="bva1"/>
</bean>
Class targetClass = Class.forName("xx.AClass");
Object bean = targetClass.newInstance();
String _setName1 = "set" +"Aaa";
Method setMethod1 = targetClass.getMethod(setName1,ava1.getClass());
setMethod1.invoke(bean,ava1);
//<property>其實就是使用反射來呼叫相應的setter方法來進行注入。

2.構造注入

public Chinese(Axe axe){
  this.axe = axe;
}
<bean id="chinese" class="xxx.Chinese">
  <constructor-arg ref="steelAxe"/>
</bean>

<bean id="stoneAxe" class="xxx.StoneAxe "/>

配置constructor-arg元素時可以指定一個index屬性, 用於指定該構造引數值將作為第幾個構造引數值, 例如,index=0表明該構造引數值將作為第一個構造引數。

兩種注入方式的對比

設值注入的優點

1.與傳統的java bean的寫法更相似,程式開發人員更容易理解,接受。 通過setter方法設定依賴關係顯得更加直觀,自然。

2.對於複雜的依賴關係,如果採用構造注入,會導致構造器過於臃腫,難以閱讀。 spring在建立bean例項時,需要同時例項化其依賴的全部例項, 因而導致效能下降。而使用設值注入則可以避免這些問題。

3.尤其是在某些屬性可選的情況下,多引數的構造器更加笨重。

構造注入的優點

1.構造注入可以在構造器中決定依賴關係的注入順序,優先依賴的優先注入。

2.對於依賴關係無需變化的bean,構造注入更有用處, 因為沒有setter方法,所有的依賴關係全部在構造器內設定。 因此無需擔心後續的程式碼對依賴關係產生破壞。

3.依賴關係只能在構造器中設定, 則只有元件的建立者才能改變元件的依賴關係,對元件的呼叫者而言, 元件內部的依賴關係完全透明,更符合高內聚的原則。

建立時機不同

設值注入是先通過無參的構造器建立一個bean例項, 然後呼叫對應的setter方法注入依賴關係;

而構造注入則直接呼叫有引數的構造器,當bean例項建立完成後, 已經完成了依賴關係的注入(在setter之前完成注入)。

依賴關係中的值要麼是一個確定的值, 要麼是spring容器中其他bean的引用。

通常情況下,spring在例項化容器時,會校驗BeanFactory中每一個bean的配置 1.bean引用的依賴bean是否指向一個合法的bean。 2.bean的普通屬性值是否獲得了一個有效值。

對於singleton作用域的bean,如果沒有強行取消其預初始化行為, 系統會在建立spring容器時預初始化所有的singleton bean, 同時該bean所依賴的bean也被一起例項化。

BeanFactory與ApplicationContext例項化容器中bean的時機不同。 BeanFactory:等到程式需要bean例項時才建立bean。

ApplicationContext:建立容器時,會預初始化容器中的全部bean。(推薦使用,前期建立消耗大,後期效能高,前期就可以檢查出配置錯誤)

關於BeanFactory與ApplicationContext會在後面的文章中揭開面紗。

總結

使用spring容器來對依賴關係進行管理,將所需例項注入給呼叫者, 不用呼叫者再收工去new,有兩種方式去注入:1.設值注入,2.構造注入。