依賴注入簡介
在介紹Dagger框架之前我們先來看看依賴注入(Dependence Injection),依賴注入還有另一個名字叫控制反轉(Inversion of Control,英文縮寫為IoC),當你看到這兩個名詞時他們大多指的都是同一個概念(實際上依賴注入是控制反轉的一部分,其另一部分稱為依賴查找(Dependenc Lookup)使用相對較少且不在本文涉及范疇),那什么是依賴注入呢?依賴了什么?又注入了什么?
不懂的東西我們自然要去問谷哥和度娘了,我搜到了一篇文章講的還滿詳細的,大家可以去學習一下:依賴注入那些事兒,這篇文章雖然是基于c#語言寫的但是用在JAVA上也是絲毫沒有問題的。
在默認大家已經讀了上面那個鏈接里的文章N遍以后,我來用簡單的語言總結一下通常的代碼結構中,調用者會直接決定實現的方式,比如調用者需要一本書的實例,在初始化的時候就會決定這本書是語文書還是數學書。而在依賴注入的世界里調用者只會聲明我需要一本書,而在執行的時候才會根據外部條件決定給調用者語文書還是數學書。
無論你是否理解了依賴注入,我們先來看下依賴注入的優缺點(這些點可能部分和Dagger框架會不一致,僅供增進大家對依賴注入的理解):
優點:
- 代碼分層:把邏輯分層后每層可以獨立的編寫和進行單元測試,而不用擔心影響到其他層的功能(只要不改變接口的實現)
- 動態配置:通過XML等方式以及反射機制可以在運行時改變程序的行為而不必重新編譯代碼
- 讓框架看起來更高端(這個是隨便BB的啦)
不足:
- 生成一個對象的步驟變復雜了(事實上操作上還是挺簡單的),對于不習慣這種方式的人,會覺得有些別扭和不直觀。
- 對象生成因為是使用反射編程,在效率上有些損耗。但相對于IoC提高的維護性和靈活性來說,這點損耗是微不足道的,除非某對象的生成對效率要求特別高。
- 缺少IDE重構操作的支持,如果在Eclipse要對類改名,那么你還需要去XML文件里手工去改了,這似乎是所有XML方式的缺憾所在。
Dagger2框架簡介及實用
以下部分內容引用自詳解Dagger2
Dagger1簡介
有Dagger2自然就有Dagger1,先簡單說下Dagger1。Dagger1框架是由Square公司受到Guice啟發創建的,也是Android上最常用的依賴注入框架
Dagger1基本特點:
- 多個注入點:依賴,通過injected
- 多種綁定方法:依賴,通過provided
- 多個modules:實現某種功能的綁定集合
- 多個對象圖: 實現一個范圍的modules集合
Dagger1是在編譯的時候實行綁定,不過也用到了反射機制。但這個反射不是用來實例化對象的,而是用于圖的構成。Dagger會在運行的時候去檢測是否一切都正常工作,所以使用的時候會付出一些代價:偶爾會無效和調試困難。
詳細的細節我們就不多說了,這不是我們關注的重點。
Dagger2簡介
Dagger2是Dagger1的分支,由谷歌公司接手開發,目前的版本是2.0。Dagger2是受到AutoValue項目的啟發。 剛開始,Dagger2解決問題的基本思想是:利用生成和寫的代碼混合達到看似所有的產生和提供依賴的代碼都是手寫的樣子。
如果我們將Dagger2和1比較,他們兩個在很多方面都非常相似,但也有很重要的區別,如下:
- 再也沒有使用反射:圖的驗證、配置和預先設置都在編譯的時候執行。
- 容易調試和可跟蹤:完全具體地調用提供和創建的堆棧
- 更好的性能:谷歌聲稱他們提高了13%的處理性能
- 代碼混淆:使用派遣方法,就如同自己寫的代碼一樣
當然所有這些很棒的特點都需要付出一個代價,那就是缺乏靈活性,例如:Dagger2沒用反射所以沒有動態機制。
Dagger2簡單實用手冊
Dagger2的詳細官方文檔可以在這里找到:http://google.github.io/dagger/,本章基本源于對官方文檔的翻譯,加上了部分自己的想法幫助大家更快理解框架內容
整個Dagger2框架是依賴于Java語言的注解(Annotation)系統實現的,在分析Espresso源碼的過程中我們會遇到如下的注解:
- @Inject
- @Component
- @Singleton
- @Module
- @Provides
- @Subcomponent
@Inject注解
@Inject用于聲明依賴,在類的構造函數上使用@Inject注解,聲明該類可以被Dagger2實例化,同時如果該構造函數是帶參數的,那么就會去使用使用了@Inject聲明的構造函數生成一個實例傳遞進來。如示例代碼中heater就會調用new Heater()方法生成一個Heater實例注入進來(Heater()方法已有@Inject注解)。特別要提到的是沒有@Inject方法注解的類是無法被Dagger2框架實例化的:
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
...
}
也可以直接對成員變量注入
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
...
}
@Provides注解滿足依賴
上文說到@Inject需要的依賴會自動調用構造方法,在有些情況下@Inject注解是無法生效的:
- Interface是無法實例化的
- 第三方庫是無法添加注解的
- 需要配置的對象在給定配置前的實例化是沒有意義的
在這種情況下我們需要@Provides注解來滿足依賴,帶有這個注解的函數的返回類型就能滿足該類型所需的實例化依賴。舉個例子,前面的Heater()方法并沒有提供注入接口,那么當我們需要一個Heater對象時,如下的方法也能夠返回一個Heater對象:
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides注解的方法也可以有自己的依賴,比如providePump方法就需要一個Thermosiphon類型的依賴:
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
@Module注解依賴圖容器
@Provides不是隨處都能使用的,是需要在@Module注解標記的類內部才能生效的
@Module
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
}
@Component注解依賴圖的根節點
前面我們提到Dagger2框架會自動生成代碼來根據依賴圖產生類的實例,比如在示例代碼中:
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
@Component的modules參數聲明了依賴Module,同時聲明一個接口及接口方法,Dagger2框架會自動生成一個以Dagger為前綴的類繼承實現這個接口,在本例中生成的類會命名為DaggerCoffeeShop,并實現maker()方法,實現的方式是一個CoffeeMaker類型的依賴,即調用者可以使用DaggerCoffeeShop().maker()方法來獲得這個依賴。當然這個書寫是有點問題的,為了讓Dagger2框架幫我自動生成一個目標類的實例,需要調用create()方法,所以它的實現應該是這樣的:
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker();
}
}
@Subcomponent子Component注解
在@Component注解的接口類的方法中,除了可以返回注入項(實現了@Inject注解的構造方法的類,可以被Dagger2框架實例化)類別以外,還可以返回@Subcomponent注解的子Component類,以實現多層調用的效果,具體請看Espresso源碼分析部分
@Singleton單例注解
這個注解是最好理解的,聲明了這個注解的實例在生成實例時都是單例化的,如果之前以及生成過改類型的實例會直接返回之前生成的實例而不是新建一個該類的新的對象