很長一段時間裏,我對控制反轉和依賴注入這兩個概念很模糊,閉上眼睛想想,總有一種眩暈的感受。但爲了成爲一名優秀的 Java 工程師,我花了一週的時間,完全把它們搞清楚了。java
在咱們編碼的過程當中,一般都須要兩個或者更多的類經過彼此的合做來實現業務邏輯,也就是說,某個對象須要獲取與其合做對象的引用,若是這個獲取的過程須要本身實現,代碼的耦合度就會高,維護起來的成本就比較高。spring
咱們來經過實戰模擬一下。假如老王是少林寺的主持,他想讓小二和尚去掃達摩院的地,代碼能夠這樣實現。app
小二類的代碼以下所示:框架
public class Xiaoer { public void saodi() { System.out.println("小二我在掃達摩院的地"); } }
老王類的代碼以下所示:ide
public class Laowang { public void mingling() { new Xiaoer().saodi(); } }
測試類的代碼以下所示:函數
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(); laowang.mingling(); } }
Laowang 類的 mingling 方法中使用 new 關鍵字建立了一個 Xiaoer 類的對象——這種代碼的耦合度就很高,維護起來的成本就很高,爲何這麼說呢?測試
某一天,達摩院的地又髒了,老王主持想起了小二和尚,可小二和尚去練易筋經了,讓誰去掃地呢,老王主持想起了小三和尚,因而 Laowang 類就不得不從新下一個新的命令,因而代碼變成了這樣:this
public class Xiaosan { public void saodi() { System.out.println("小三我在掃達摩院的地"); } } public class Laowang { public void mingling() { new Xiaoer().saodi(); } public void mingling1() { new Xiaosan().saodi(); } }
假如小三和尚去挑水了,老王主持沒準要下命令給小四和尚去掃達摩院的地。這樣下去的話,Laowang 這個類會瘋掉的。編碼
老王主持以爲本身堂堂一屆高僧,下個掃地的命令居然這樣麻煩,他以爲很不爽。spa
咱們得替老王主持想個辦法對不對?
不如把這個掃地的差事交給老王的師弟老方吧,老方負責去叫小二和尚仍是小三和尚仍是小四和尚去執行老王主持的命令。代碼能夠這樣實現。
定義一個掃地和尚的接口,代碼以下所示:
public interface Heshang { void saodi(); }
小二類的代碼修改以下所示:
public class Xiaoer implements Heshang { @Override public void saodi() { System.out.println("小二我在掃達摩院的地"); } public boolean isYijinjing() { // 星期三的時候小二和尚要練易筋經 return false; } }
小三類的代碼修改以下所示:
public class Xiaosan implements Heshang { @Override public void saodi() { System.out.println("小三我在掃達摩院的地"); } }
老方類的代碼以下所示:
public class Laofang { public static Heshang getSaodiseng() { Xiaoer xiaoer = new Xiaoer(); if (xiaoer.isYijinjing()) { return new Xiaosan(); } return xiaoer; } }
若是老方確認小二和尚在練易筋經,就叫小三和尚。
老王類的代碼修改以下所示:
public class Laowang { public void mingling() { Laofang.getSaodiseng().saodi(); } }
測試類的代碼不改變,以下所示:
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(); laowang.mingling(); } }
老王如今是否是省心多了,他只管下命令,該叫誰去掃達摩院的地由他師弟老方去負責。
咱們替老王想的這個辦法就叫控制反轉(Inversion of Control,縮寫爲 IoC),它不是一種技術,而是一種思想——指導咱們設計出鬆耦合的程序。
控制反轉從詞義上能夠拆分爲「控制」和「反轉」,說到控制,就必須找出主語和賓語,誰控制了誰;說到反轉,就必須知道正轉是什麼。
你看,在緊耦合的狀況下,老王下命令的時候本身要經過 new 關鍵字建立依賴的對象(小二和尚或者小三和尚);而控制反轉後,老王要找的掃地和尚由他師弟老方負責,也就是說控制權交給了老方,是否是反轉了呢?
依賴注入(Dependency Injection,簡稱 DI)是實現控制反轉的主要方式:在類 A 的實例建立過程當中就建立了依賴的 B 對象,經過類型或名稱來判斷將不一樣的對象注入到不一樣的屬性中。大概有 3 種具體的實現形式:
1)基於構造函數。實現特定參數的構造函數,在新建對象時傳入所依賴類型的對象。
老王類的代碼修改以下所示:
public class Laowang { private Heshang saodiseng; public Laowang(Heshang saodiseng) { this.saodiseng = saodiseng; } public void mingling() { this.saodiseng.saodi(); } }
測試類的代碼修改以下所示:
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(new Xiaosan()); laowang.mingling(); } }
這時候,控制權掌握在測試類的手裏,它決定派小二和尚仍是小三和尚去執行老王的掃地命令。
2)基於 set 方法。實現特定屬性的 public set 方法,讓外部容器調用傳入所依賴類型的對象。
老王類的代碼修改以下所示:
public class Laowang { private Heshang saodiseng; public Heshang getSaodiseng() { return saodiseng; } public void setSaodiseng(Heshang saodiseng) { this.saodiseng = saodiseng; } public void mingling() { this.getSaodiseng().saodi(); } }
測試類的代碼修改以下所示:
public class Test { public static void main(String[] args) { Laowang laowang = new Laowang(); Xiaosan xiaosan = new Xiaosan(); laowang.setSaodiseng(xiaosan); laowang.mingling(); } }
這時候,控制權仍然掌握在測試類的手裏,它決定派小二和尚仍是小三和尚去執行老王的掃地命令。
3)基於接口。實現特定接口以供外部容器注入所依賴類型的對象,這種作法比較構造函數和 set 方法更爲複雜,這裏就此略過。
可能有人會把控制反轉等同於依賴注入,但實際上它們有着本質上的不一樣:控制反轉是一種思想,而依賴注入是實現控制反轉的一種形式。
當咱們搞清楚控制反轉和依賴注入的概念後,就能夠順帶了解一下大名鼎鼎的 Spring 框架。控制反轉是 Spring 框架的核心,貫穿始終。Spring 中依賴注入有兩種實現方式:set 方式(傳值方式)和構造器方式(引用方式)。
首先,咱們須要在 pom.xml 文件中加入 Spring 的依賴項,代碼以下所示:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.2.RELEASE</version> </dependency>
其次,咱們將 Laowang 類修改成以下內容:
public class Laowang { private Heshang saodiseng; public Laowang(Heshang saodiseng) { this.saodiseng = saodiseng; } public void mingling() { this.saodiseng.saodi(); } }
而後,咱們建立一個 Spring 的配置文件 application.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.xsd"> <bean id="laowang" class="com.cmower.java_demo.ioc.Laowang"> <constructor-arg ref="saodiseng" /> </bean> <bean id="saodiseng" class="com.cmower.java_demo.ioc.Xiaosan" /> </beans>
經過 <bean> 元素配置了兩個對象,一個老王主持,一個小三和尚,使用 <constructor-arg> 元素將小三和尚做爲老王主持的構造參數。
準備工做完成之後,咱們來測試一下,代碼示例以下:
public class Test { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); Laowang laowang = (Laowang) context.getBean("laowang"); laowang.mingling(); } }
你看,咱們將控制權交給了 IoC 框架 Spring,這樣也能夠完美的解決代碼耦合度較緊的問題。
總結一下:
1)控制反轉是一種在軟件工程中解耦合的思想,把控制權交給了第三方,在運行的時候由第三方決定將具體的依賴對象「注入」到調用類的對象中。
2)依賴注入能夠做爲控制反轉的一種實現方式,將實例變量傳入到一個對象中去。
3)經過 IoC 框架,類 A 依賴類 B 的強耦合關係能夠在運行時經過容器創建,也就是說把建立 B 實例的工做移交給容器,類 A 只管使用就能夠。