引述:IoC(控制反轉:Inverse of Control)是Spring容器的內核,AOP、聲明式事務等功能在此基礎上開花結果。可是IoC這個重要的概念卻比較晦澀隱諱,不容易讓人望文生義,這不能不說是一大遺憾。不過IoC確實包括不少內涵,它涉及代碼解耦、設計模式、代碼優化等問題的考量,咱們打算經過一個小例子來講明這個概念。
經過實例理解IoC的概念
賀歲大片在中國已經造成了一個傳統,每到年末總有多部賀歲大片紛至沓來讓人目不暇接。在全部賀歲大片中,張之亮的《墨攻》算是比較出彩的一部。該片講述了戰國時期墨家人革離幫助梁國反抗趙國侵略的我的英雄主義故事,恢宏壯闊、渾雄凝重的歷史場面至關震撼。其中有一個場景:當劉德華所飾演的墨者革離到達梁國都城下,城上樑國守軍問到:「來者何人?」劉德華回答:「墨者革離!」咱們不妨經過一個Java類爲這個「城門叩問」的場景進行編劇,並藉此理解IoC的概念:
代碼清單3-1 MoAttack:經過演員安排劇本
java
public class MoAttack { public void cityGateAsk(){ //①演員直接侵入劇本 LiuDeHua ldh = new LiuDeHua(); ldh.responseAsk("墨者革離!"); } }
咱們會發現以上劇本在①處,做爲具體角色飾演者的劉德華直接侵入到劇本中,使劇本和演員直接耦合在一塊兒(圖3-1)。
一個明智的編劇在劇情創做時應圍繞故事的角色進行,而不該考慮角色的具體飾演者,這樣纔可能在劇本投拍時自由地遴選任何適合的演員,而非綁定在劉德華一人身上。經過以上的分析,咱們知道須要爲該劇本主人公革離定義一個接口:
代碼清單3-2 MoAttack:引入劇本角色
spring
public class MoAttack { public void cityGateAsk() { //①引入革離角色接口 GeLi geli = new LiuDeHua(); //②經過接口開展劇情 geli.responseAsk("墨者革離!"); } }
在①處引入了劇本的角色——革離,劇本的情節經過角色展開,在拍攝時角色由演員飾演,如②處所示。所以墨攻、革離、劉德華三者的類圖關係如圖 3 2所示:
但是,從圖3 2中,咱們能夠看出MoAttack同時依賴於GeLi接口和LiuDeHua類,並無達到咱們所指望的劇本僅依賴於角色的目的。可是角色最終必須經過具體的演員才能完成拍攝,如何讓LiuDeHua和劇本無關而又能完成GeLi的具體動做呢?固然是在影片投拍時,導演將LiuDeHua安排在GeLi的角色上,導演將劇本、角色、飾演者裝配起來(圖3-3)。
經過引入導演,使劇本和具體飾演者解耦了。對應到軟件中,導演像是一個裝配器,安排演員表演具體的角色。
如今咱們能夠反過來說解IoC的概念了。IoC(Inverse of Control)的字面意思是控制反轉,它包括兩個內容:
設計模式
那究竟是什麼東西的「控制」被「反轉」了呢?對應到前面的例子,「控制」是指選擇GeLi角色扮演者的控制權;「反轉」是指這種控制權從《墨攻》劇本中移除,轉交到導演的手中。對於軟件來講,便是某一接口具體實現類的選擇控制權從調用類中移除,轉交給第三方決定。
由於IoC確實不夠開門見山,所以業界曾進行了普遍的討論,最終軟件界的泰斗級人物Martin Fowler提出了DI(依賴注入:Dependency Injection)的概念用以代替IoC,即讓調用類對某一接口實現類的依賴關係由第三方(容器或協做類)注入,以移除調用類對某一接口實現類的依賴。「依賴注入」這個名詞顯然比「控制反轉」直接明瞭、易於理解。
IoC的類型
從注入方法上看,主要能夠劃分爲三種類型:構造函數注入、屬性注入和接口注入。Spring支持構造函數注入和屬性注入。下面咱們繼續使用以上的例子說明這三種注入方法的區別。
構造函數注入
在構造函數注入中,咱們經過調用類的構造函數,將接口實現類經過構造函數變量傳入,如代碼清單3-3所示:
代碼清單3-3 MoAttack:經過構造函數注入革離扮演者
函數
public class MoAttack { private GeLi geli; //①注入革離的具體扮演者 public MoAttack(GeLi geli){ this.geli = geli; } public void cityGateAsk(){ geli.responseAsk("墨者革離!"); } }
MoAttack的構造函數不關心具體是誰扮演革離這個角色,只要在①處傳入的扮演者按劇本要求完成相應的表演便可。角色的具體扮演者由導演來安排,如代碼清單3-4所示:
代碼清單3-4 Director:經過構造函數注入革離扮演者
優化
public class Director { public void direct(){ //①指定角色的扮演者 GeLi geli = new LiuDeHua(); //②注入具體扮演者到劇本中 MoAttack moAttack = new MoAttack(geli); moAttack.cityGateAsk(); } }
在①處,導演安排劉德華飾演革離的角色,並在②處,將劉德華「注入」到墨攻的劇本中,而後開始「城門叩問」劇情的演出工做。
屬性注入
有時,導演會發現,雖然革離是影片《墨攻》的第一主角,但並不是每一個場景都須要革離的出現,在這種狀況下經過構造函數注入至關於每時每刻都在革離的飾演者在場,可見並不穩當,這時能夠考慮使用屬性注入。屬性注入能夠有選擇地經過Setter方法完成調用類所需依賴的注入,更加靈活方便:
代碼清單3-5 MoAttack:經過Setter方法注入革離扮演者
this
public class MoAttack { private GeLi geli; //①屬性注入方法 public void setGeli(GeLi geli) { this.geli = geli; } public void cityGateAsk() { geli.responseAsk("墨者革離"); } }
MoAttack在①處爲geli屬性提供一個Setter方法,以便讓導演在須要時注入geli的具體扮演者。
代碼清單3-6 Director:經過Setter方法注入革離扮演者
spa
public class Director { public void direct(){ GeLi geli = new LiuDeHua(); MoAttack moAttack = new MoAttack(); //①調用屬性Setter方法注入 moAttack.setGeli(geli); moAttack.cityGateAsk(); } }
和經過構造函數注入革離扮演者不一樣,在實例化MoAttack劇本時,並未指定任何扮演者,而是在實例化MoAttack後,在須要革離出場時,才調用其setGeli()方法注入扮演者。按照相似的方式,咱們還能夠分別爲劇本中其餘諸如梁王、巷淹中等角色提供注入的Setter方法,這樣,導演就能夠根據所拍劇段的不一樣,注入相應的角色了。
接口注入
將調用類全部依賴注入的方法抽取到一個接口中,調用類經過實現該接口提供相應的注入方法。爲了採起接口注入的方式,必須先聲明一個ActorArrangable接口:
設計
public interface ActorArrangable { void injectGeli(GeLi geli); }
而後,MoAttack實現ActorArrangable接口提供具體的實現:
代碼清單3-7 MoAttack:經過接口方法注入革離扮演者
code
public class MoAttack implements ActorArrangable { private GeLi geli; //①實現接口方法 public void injectGeli (GeLi geli) { this.geli = geli; } public void cityGateAsk() { geli.responseAsk("墨者革離"); } }
Director經過ActorArrangable的injectGeli()方法完成扮演者的注入工做。
代碼清單3-8 Director:經過接口方法注入革離扮演者
xml
public class Director { public void direct(){ GeLi geli = new LiuDeHua(); MoAttack moAttack = new MoAttack(); moAttack. injectGeli (geli); moAttack.cityGateAsk(); } }
因爲經過接口注入須要額外聲明一個接口,增長了類的數目,並且它的效果和屬性注入並沒有本質區別,所以咱們不提倡採用這種方式。
經過容器完成依賴關係的注入
雖然MoAttack和LiuDeHua實現瞭解耦,MoAttack無須關注角色實現類的實例化工做,但這些工做在代碼中依然存在,只是轉移到Director類中而已。假設某一製片人想改變這一局面,在選擇某個劇本後,但願經過一個「海選」或者第三中介機構來選擇導演、演員,讓他們各司其職,那劇本、導演、演員就都實現解耦了。
所謂媒體「海選」和第三方中介機構在程序領域便是一個第三方的容器,它幫助完成類的初始化與裝配工做,讓開發者從這些底層實現類的實例化、依賴關係裝配等工做中脫離出來,專一於更有意義的業務邏輯開發工做。這無疑是一件使人嚮往的事情,Spring就是這樣的一個容器,它經過配置文件或註解描述類和類之間的依賴關係,自動完成類的初始化和依賴注入的工做。下面是Spring配置文件的對以上實例進行配置的配置文件片段:
<?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="geli" class="LiuDeHua"/> <bean id="moAttack" class="com.baobaotao.ioc.MoAttack" p:geli-ref="geli"/><!--②經過geli-ref創建依賴關係--> </beans>
經過new XmlBeanFactory(「beans.xml」)等方式便可啓動容器。在容器啓動時,Spring根據配置文件的描述信息,自動實例化Bean並完成依賴關係的裝配,從容器中便可返回準備就緒的Bean實例,後續可直接使用之。 Spring爲何會有這種「神奇」的力量,僅憑一個簡單的配置文件,就能魔法般地實例化並裝配好程序所用的Bean呢?這種「神奇」的力量歸功於Java語言自己的類反射功能。