聲明:文章的前三部分參考博文:https://www.cnblogs.com/Nouno...
這篇文章首發是在個人我的微信訂閱號天天學編程,關注個人微信訂閱號查看更多文章。html
咱們都知道,在採用面向對象方法設計的軟件系統中,它的底層實現都是由N個對象組成的,全部的對象經過彼此的合做,最終實現系統的業務邏輯。java
圖1:軟件系統中耦合的對象spring
若是咱們打開機械式手錶的後蓋,就會看到與上面相似的情形,各個齒輪分別帶動時針、分針和秒 針順時針旋轉,從而在錶盤上產生正確的時間。圖1中描述的就是這樣的一個齒輪組,它擁有多個獨立的齒輪,這些齒輪相互齧合在一塊兒,協同工做,共同完成某項 任務。咱們能夠看到,在這樣的齒輪組中,若是有一個齒輪出了問題,就可能會影響到整個齒輪組的正常運轉。
齒輪組中齒輪之間的齧合關係,與軟件系統中對象之間的耦合關係很是類似。對象之間的耦合關係是沒法避免的,也是必要的,這是協同工做的基礎。如今,伴隨着 工業級應用的規模愈來愈龐大,對象之間的依賴關係也愈來愈複雜,常常會出現對象之間的多重依賴性關係,所以,架構師和設計師對於系統的分析和設計,將面臨 更大的挑戰。對象之間耦合度太高的系統,必然會出現牽一髮而動全身的情形。編程
圖2:對象之間複雜的依賴關係微信
耦合關係不只會出如今對象與對象之間,也會出如今軟件系統的各模塊之間,以及軟件系統和硬件系統之間。如何下降系統之間、模塊之間和對象之間的耦合度,是軟件工程永遠追求的目標之一。爲了解決對象之間的耦合度太高的問題,軟件專家Michael Mattson提出了IOC理論,用來實現對象之間的「解耦」,目前這個理論已經被成功地應用到實踐當中,不少的J2EE項目均採用了IOC框架產品Spring。架構
IOC是Inversion of Control的縮寫,多數書籍翻譯成「控制反轉」,還有些書籍翻譯成爲「控制反向」或者「控制倒置」。
1996年,Michael Mattson在一篇有關探討面向對象框架的文章中,首先提出了IOC 這個概念。對於面向對象設計及編程的基本思想,前面咱們已經講了不少了,再也不贅述,簡單來講就是把複雜系統分解成相互合做的對象,這些對象類經過封裝以 後,內部實現對外部是透明的,從而下降了解決問題的複雜度,並且能夠靈活地被重用和擴展。IOC理論提出的觀點大致是這樣的:藉助於「第三方」實現具備依 賴關係的對象之間的解耦,以下圖:框架
圖3:IOC解耦過程函數
你們看到了吧,因爲引進了中間位置的「第三方」,也就是IOC容器,使得A、B、C、D這4個對象沒有了耦合關係,齒輪之間的傳動所有依靠「第三 方」了,所有對象的控制權所有上繳給「第三方」IOC容器,因此,IOC容器成了整個系統的關鍵核心,它起到了一種相似「粘合劑」的做用,把系統中的全部 對象粘合在一塊兒發揮做用,若是沒有這個「粘合劑」,對象與對象之間會彼此失去聯繫,這就是有人把IOC容器比喻成「粘合劑」的由來。
咱們再來作個試驗:把上圖中間的IOC容器拿掉,而後再來看看這套系統:學習
圖4:拿掉IoC容器後的系統this
咱們如今看到的畫面,就是咱們要實現整個系統所須要完成的所有內容。這時候,A、B、C、D這4個對象之間已經沒有了耦合關係,彼此毫無聯繫,這樣 的話,當你在實現A的時候,根本無須再去考慮B、C和D了,對象之間的依賴關係已經下降到了最低程度。因此,若是真能實現IOC容器,對於系統開發而言, 這將是一件多麼美好的事情,參與開發的每一成員只要實現本身的類就能夠了,跟別人沒有任何關係!
咱們再來看看,控制反轉(IOC)到底爲何要起這麼個名字?咱們來對比一下:
軟件系統在沒有引入IOC容器以前,如圖1所示,對象A依賴於對象B,那麼對象A在初始化或者運行到某一點的時候,本身必須主動去建立對象B或者使用已經建立的對象B。不管是建立仍是使用對象B,控制權都在本身手上。
軟件系統在引入IOC容器以後,這種情形就徹底改變了,如圖3所示,因爲IOC容器的加入,對象A與對象B之間失去了直接聯繫,因此,當對象A運行到須要對象B的時候,IOC容器會主動建立一個對象B注入到對象A須要的地方。
經過先後的對比,咱們不難看出來:對象A得到依賴對象B的過程,由主動行爲變爲了被動行爲,控制權顛倒過來了,這就是「控制反轉」這個名稱的由來。
2004年,Martin Fowler探討了同一個問題,既然IOC是控制反轉,那麼究竟是「哪些方面的控制被反轉了呢?」,通過詳細地分析和論證後,他得出了答案:「得到依賴對 象的過程被反轉了」。控制被反轉以後,得到依賴對象的過程由自身管理變爲了由IOC容器主動注入。因而,他給「控制反轉」取了一個更合適的名字叫作「依賴 注入(Dependency Injection)」。他的這個答案,實際上給出了實現IOC的方法:注入。所謂依賴注入,就是由IOC容器在運行期間,動態地將某種依賴關係注入到對 象之中。
因此,依賴注入(DI)和控制反轉(IOC)是從不一樣的角度的描述的同一件事情,就是指經過引入IOC容器,利用依賴關係注入的方式,實現對象之間的解耦。
咱們舉一個生活中的例子,來幫助理解依賴注入的過程。你們對USB接口和USB設備應該都很熟悉吧,USB爲咱們使用電腦提供了很大的方便,如今有不少的外部設備都支持USB接口。
圖5:USB接口和USB設備
如今,咱們利用電腦主機和USB接口來實現一個任務:從外部USB設備讀取一個文件。
電腦主機讀取文件的時候,它一點也不會關心USB接口上鍊接的是什麼外部設備,並且它確實也無須知道。它的任務就是讀取USB接口,掛接的外部設備只要符 合USB接口標準便可。因此,若是我給電腦主機鏈接上一個U盤,那麼主機就從U盤上讀取文件;若是我給電腦主機鏈接上一個外置硬盤,那麼電腦主機就從外置 硬盤上讀取文件。掛接外部設備的權力由我做主,即控制權歸我,至於USB接口掛接的是什麼設備,電腦主機是決定不了,它只能被動的接受。電腦主機須要外部 設備的時候,根本不用它告訴我,我就會主動幫它掛上它想要的外部設備,你看個人服務是多麼的到位。這就是咱們生活中常見的一個依賴注入的例子。在這個過程 中,我就起到了IOC容器的做用。
經過這個例子,依賴注入的思路已經很是清楚:當電腦主機讀取文件的時候,我就把它所要依賴的外部設備,幫他掛接上。整個外部設備註入的過程和一個被依賴的對象在系統運行時被注入另一個對象內部的過程徹底同樣。
咱們把依賴注入應用到軟件系統中,再來描述一下這個過程:
對象A依賴於對象B,當對象 A須要用到對象B的時候,IOC容器就會當即建立一個對象B送給對象A。IOC容器就是一個對象製造工廠,你須要什麼,它會給你送去,你直接使用就好了, 而不再用去關心你所用的東西是如何製成的,也不用關心最後是怎麼被銷燬的,這一切所有由IOC容器包辦。
在傳統的實現中,由程序內部代碼來控制組件之間的關係。咱們常用new關鍵字來實現兩個組件之間關係的組合,這種實現方式會形成組件之間耦合。IOC 很好地解決了該問題,它將實現組件間關係從程序內部提到外部容器,也就是說由容器在運行期將組件間的某種依賴關係動態注入組件中。
最後,咱們用兩個實例來看DI的注入方式,代碼也是十分簡單的!
ClassB 類經過類構造函數被注入到 ClassA 類中
ClassB.java文件
package com.wangc; public class ClassB { public ClassB() { System.out.println("hello,我在ClassB的構造器裏!"); } public void say() { System.out.println("hello,我是ClassB!"); } }
ClassA.java文件
package com.wangc; public class ClassA { private ClassB classB; public ClassA(ClassB classB) { System.out.println("hello,我在ClassA的構造器裏!"); this.classB = classB; } public void sayHello() { classB.say(); } }
Beans.xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="classA" class="com.wangc.ClassA"> <!-- 若是你要把一個引用傳遞給一個對象,那麼你須要使用標籤的 ref 屬性,而若是你要直接傳遞一個值,那麼你應該使用 value 屬性。 --> <constructor-arg ref="classB" /> </bean> <bean id="classB" class="com.wangc.ClassB"></bean> </beans>
MainApp.java文件
package com.wangc; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MainApp { public static void main(String[] args) { ApplicationContext context =new ClassPathXmlApplicationContext("Beans.xml"); ClassA classA = (ClassA) context.getBean("classA"); classA.sayHello(); } }
運行結果:
hello,我在ClassB的構造器裏! hello,我在ClassA的構造器裏! hello,我是ClassB!
當容器調用一個無參的構造函數或一個無參的靜態 factory 方法來初始化你的 bean 後,經過容器在你的 bean 上調用設值函數,基於設值函數的 DI 就完成了。 將上面例子中的ClassA.java文件和Beans.xml文件做以下改動,其餘文件不變。
ClassA.java文件
package com.wangc; public class ClassA { private ClassB classB; public ClassB getClassB() { return classB; } public void setClassB(ClassB classB) { System.out.println("hello,我在setClassB裏!"); this.classB = classB; } public void sayHello() { classB.say(); } }
Beans.xml文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="classA" class="com.wangc.ClassA"> <!-- 若是你要把一個引用傳遞給一個對象,那麼你須要使用標籤的 ref 屬性,而若是你要直接傳遞一個值,那麼你應該使用 value 屬性。 --> <property name="classB" ref="classB" /> </bean> <bean id="classB" class="com.wangc.ClassB"></bean> </beans>
運行結果:
hello,我在ClassB的構造器裏! hello,我在setClassB裏! hello,我是ClassB!
QQ學習交流羣:713479727
微信學習交流羣:微信羣加入方式【公衆號下方菜單欄-->學習資源-->微信羣】
微信公衆號:EverydayCoding 或掃描下方二維碼
歡迎你們加入。。。