[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操作)。因此,也可以說在物件如何獲取它的依賴物件這件事情上,控制權反轉了。這便不難理解控制反轉和依賴注入這兩個名字的由來了。
一個場景
上面的解釋聽起來還是有點晦澀,讓我們來看看具體的例子吧!
有個土豪老闆,我們經常要出差,因此經常要訂機票。定機票呢,可以通過去哪兒網訂票,也可以通過攜程訂票。
我們馬上可以想到可以通過三個類來表達這個場景,Boss
,QunarBookingService
,CtripBookingService
。當然了,我們還應該提供一個BookingService
QunarBookingService
,CtripBookingService
的公共抽象。面向介面程式設計是面向物件設計的基本原則,如果這都不瞭解,趕緊先回去看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一下就輕鬆搞定!Boss
和QunarBookingService
彼此不知道對方,Spring幫兩者粘合在一起。
為什麼IoC是個大招,因為它會自然而然得促進你應用一些好的設計原則,會幫助你開發出更加“高內聚低耦合”的軟體。
IoC的實現
最後我們簡單說說IoC是如何實現的。想象一下如果我們自己來實現這個依賴注入的功能,我們怎麼來做? 無外乎:
- 讀取標註或者配置檔案,看看Boss依賴的是哪個OrderService,拿到類名
- 使用
反射
的API,基於類名例項化對應的物件例項 - 將物件例項,通過建構函式或者setter,傳遞給Boss
我們發現其實自己來實現也不是很難,Spring實際也就是這麼做的。這麼看的話其實IoC就是一個工廠模式的升級版!當然要做一個成熟的IoC框架,還是非常多細緻的工作要做,Spring不僅提供了一個已經成為業界標準的Java IoC框架,還提供了更多強大的功能,所以大家就別去造輪子啦!希望瞭解IoC更多實現細節不妨通過學習Spring的原始碼來加深理解!