1. 程式人生 > >[spring入門學習筆記][spring的IoC原理]

[spring入門學習筆記][spring的IoC原理]

什麼叫IoC

控制反轉Inversion of Control,縮寫為IoC),是面向物件程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合度。其中最常見的方式叫做依賴注入Dependency Injection,簡稱DI),還有一種方式叫“依賴查詢”(Dependency Lookup)。通過控制反轉,物件在被建立的時候,由一個調控系統內所有物件的外界實體,將其所依賴的物件的引用傳遞給它。也可以說,依賴被注入到物件中。`

高內聚低耦合可以說是軟體技術形態的終極目標。用學術界的話來說,軟體的兩個本質特性就是構造性和演化性,高內聚低耦合的設計能夠讓構造和演化都更加高效,比如:

  • 開發更方便組織分工
  • 程式碼更容易進行復用
  • 更容易進行測試
  • 軟體演化有更好的靈活性,能快速響應需求變化,維護代價更小

軟體設計各種技術的出現,無一不是朝著這個終極目標的努力。面向物件、基於元件(學術界稱為構件)的軟體開發、面向切面程式設計(AOP)、Java近些年流行的模組化方法(比如OSGi技術)等等,這些方法和技術的出現,無外乎都是為了讓軟體更加高內聚低耦合。與此同時,各路大神還提出各種軟體設計原則和模式,來規範我們的軟體形態。我們今天談的IoC也是其中的一個大招。IoC(Inversion of Control​,控制反轉)也稱為依賴注入(Dependency Injection),作為Spring的一個核心思想,是一種設計物件之間依賴關係的原則及其相關技術。

先來看看字面上怎麼來解釋:當一個物件建立時,它所依賴的物件由外部傳遞給它,而非自己去建立所依賴的物件(比如通過new操作)。因此,也可以說在物件如何獲取它的依賴物件這件事情上,控制權反轉了。這便不難理解控制反轉和依賴注入這兩個名字的由來了。

一個場景

上面的解釋聽起來還是有點晦澀,讓我們來看看具體的例子吧!

有個土豪老闆,我們經常要出差,因此經常要訂機票。定機票呢,可以通過去哪兒網訂票,也可以通過攜程訂票。

我們馬上可以想到可以通過三個類來表達這個場景,BossQunarBookingServiceCtripBookingService。當然了,我們還應該提供一個BookingService

介面,作為QunarBookingServiceCtripBookingService的公共抽象。面向介面程式設計是面向物件設計的基本原則,如果這都不瞭解,趕緊先回去看GoF的《設計模式》第一章!

BookingService.java

package com.tianmaying.iocdemo;

public interface BookingService {
    void bookFlight();
}

QunarBookingService.java

package com.tianmaying.iocdemo;

public class QunarBookingService implements BookingService {
    public void bookFlight() {
        System.out.println("book fight by Qunar!");

    }
}

CtripBookingService.java

package com.tianmaying.iocdemo;

public class CtripBookingService implements BookingService {
    public void bookFlight() {
        System.out.println("book fight by Ctrip!");
    }
}

好了,土豪出門談生意,得訂機票了,Boss就琢磨著怎麼訂票呢,Boss比較了一下價格,這一次決定用去哪兒,對應的Boss的程式碼:

Boss.java

package com.tianmaying.iocdemo;

public class Boss {

    private BookingService bookingService;

    public Boss() {
        this.bookingService = new QunarBookingService();
    }

    public BookingService getBookingService() {
        return bookingService;
    }

    public void setBookingService(BookingService bookingService) {
        this.bookingService = bookingService;
    }

    public void goSomewhere() {
        bookingService.bookFlight();
    }

在Boss的建構函式中,將其orderService成員變數例項化為​QunarOrderService,goSomewhere()函式中就可以呼叫orderService的bookFlight方法了!

為了把這個場景Run起來,我們還需要一個main函式:

package com.tianmaying.iocdemo;

public class App {
    public static void main(String[] args) {
        bossGoSomewhere();
    }

    static void bossGoSomewhere() {
        Boss boss = new Boss();
        boss.goSomewhere();
    }
}

執行之後可以看到控制中可以打印出”book fight by Qunar!”了。

使用IoC的場景

在這個例子中,我們看到Boss需要使用OrderService,於是Boss自己例項化了一個QunarOrderService物件。同志們想想,身為土豪Boss,思考的都是公司戰略的事兒,定個票還要自己選擇通過什麼方式來完成,這個Boss是不是當得實在太苦逼。

所以土豪趕緊給自己找了個美女祕書(別想歪!),Boss要出差時,只需要說一聲他需要訂票服務,至於是哪個服務,讓美女祕書選好後告訴他即可(注入啊!注入!)。(別跟我較真說美女祕書直接把票送上就行!)

這樣的話,Boss是不是一身輕鬆了? 而這個美女祕書還是免費包郵的,這正是Spring扮演的角色!來看看使用Spring之後的程式碼。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.2.0.RELEASE</version>
</dependency>

QunarBookingService.java

package com.tianmaying.iocdemo;
import org.springframework.stereotype.Component;

@Component
public class QunarBookingService implements BookingService {
    public void bookFlight() {
        System.out.println("book fight by Qunar!");

    }
}

這裡我們使用Spring的@Component標註將QunarBookingService註冊進Spring的Context,這樣它就可以被注入到需要它的地方!相應地,建立QunarBookingService例項的責任也交給了Spring。我們說了,美女祕書幫你搞定嘛!

新建一個SmartBoss類,聰明的老闆知道把選擇訂機票服務這樣的雜事交給祕書來做。

SmartBoss.java

package com.tianmaying.iocdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class SmartBoss {
    private BookingService bookingService;

    @Autowired
    public void setBookingService(BookingService bookingService) {
        this.bookingService = bookingService;
    }

    public BookingService getBookingService() {
        return bookingService;
    }

    public void goSomewhere() {
        bookingService.bookFlight();
    }
}

在上面的程式碼中,SmartBoss不再自己建立BookingService的例項,只是通過@Autowired標註告訴Spring小祕我需要一個BookingService!

呼叫程式碼因此也要做一些小修改,需要建立Spring的Context:

static void smartBossGoSomewhere() {
    AbstractApplicationContext context = new AnnotationConfigApplicationContext(
            App.class);
    try {
        SmartBoss boss = context.getBean(SmartBoss.class);
        boss.goSomewhere();
    } finally {
        context.close();
    }
}

IoC的好處

回到正題,通過上面的例子,我們來看看IoC到底帶來了哪些好處?

Boss沒有和某個具體的BookingService類耦合到一起了,這樣Boss的維護和演化就更加方便。想象一下,如果Boss需要改用CtripBookingService,這時也不需要修改Boss.java的程式碼,更換介面的實現非常方便,給Boss注入新的實現即可,輕鬆愜意。(當然,要做到熱插拔還 需要進一步的工作,要麼得玩轉類載入器這玩意,或者藉助OSGi這樣的神器)。這也是典型的開放-封閉原則的例子,即對現有模組,功能擴充套件應該是開放的,而對其程式碼修改應該是封閉的,即能夠做到不需要修改已有程式碼來擴充套件新的功能。

想象一下,如果Boss自己直接去例項化QunarBookingService,而QunarBookingService在另外一個Package中甚至另外一個Jar包中,你可得import進來才能使用,緊耦合啊!現在好了,Boss只依賴於抽象介面,測試更方便了吧,Mock一下就輕鬆搞定!BossQunarBookingService彼此不知道對方,Spring幫兩者粘合在一起。

為什麼IoC是個大招,因為它會自然而然得促進你應用一些好的設計原則,會幫助你開發出更加“高內聚低耦合”的軟體。

IoC的實現

最後我們簡單說說IoC是如何實現的。想象一下如果我們自己來實現這個依賴注入的功能,我們怎麼來做? 無外乎:

  1. 讀取標註或者配置檔案,看看Boss依賴的是哪個OrderService,拿到類名
  2. 使用反射的API,基於類名例項化對應的物件例項
  3. 將物件例項,通過建構函式或者setter,傳遞給Boss

我們發現其實自己來實現也不是很難,Spring實際也就是這麼做的。這麼看的話其實IoC就是一個工廠模式的升級版!當然要做一個成熟的IoC框架,還是非常多細緻的工作要做,Spring不僅提供了一個已經成為業界標準的Java IoC框架,還提供了更多強大的功能,所以大家就別去造輪子啦!希望瞭解IoC更多實現細節不妨通過學習Spring的原始碼來加深理解!