為什么使用Dagger2
無論是構造函數注入還是接口注入,都避免不了要編寫大量的模板代碼。機智的猿猿們當然不開心做這些重復性的工作,于是各種依賴注入框架應用而生。但是這么多的依賴注入框架為什么我們卻偏愛Dagger2呢?我們先從Spring中的控制反轉(IOC)說起。
談起依賴注入,做過J2EE開發的同學一定會想起Spring IOC,那通過迷之XML來配置依賴的方式真的很讓人討厭;而且XML與Java代碼分離也導致代碼鏈難以追蹤。之后更加先進的Guice(Android端也有個RoboGuice)出現了,我們不再需要通過XML來配置依賴,但其運行時實現注入的方式讓我們在追蹤和定位錯誤的時候卻又萬分痛苦。開篇提到過Dagger就是受Guice的啟發而開發出來的;Dagger繼承了前輩的思想,在性能又碾壓了它的前輩Guice,可謂是長江后浪推前浪,前浪死在沙灘上。
又如開篇我在簡介中說到的,Dagger是一種半靜態半運行時的DI框架,雖說依賴注入是完全靜態的,但是生成有向無環圖(DAG)還是基于反射來實現,這無論在大型的服務端應用還是在Android應用上都不是最優方案。升級版的Dagger2解決了這一問題,從半靜態變為完全靜態,從Map式的API變成申明式API(@Module),生成的代碼更優雅高效;而且一旦出錯我們在編譯期間就能發現。所以Dagger2對開發者的更加友好了,當然Dagger2也因此喪失了一些靈活性,但總體來說利還是遠遠大于弊的。
前面提到這種A B C D E連續依賴的問題,一旦E的創建方式發生了改變就會引發連鎖反應,可能會導致A B C D都需要做針對性的修改;但是騷年,你以為為這僅僅是工作量的問題嗎?更可怕的是我們創建A時需要按順序先創建E D C B四個對象,而且必須保證順序上是正確的。Dagger2就很好的解決了這一問題(不只是Dagger2,在其他DI框架中開發者同樣不需要關注這些問題)。
Dagger2注解
開篇我們就提到Dagger2是基于Java注解來實現依賴注入的,那么在正式使用之前我們需要先了解下Dagger2中的注解。Dagger2使用過程中我們通常接觸到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。
@Inject:@Inject有兩個作用,一是用來標記需要依賴的變量,以此告訴Dagger2為它提供依賴;二是用來標記構造函數,Dagger2通過@Inject注解可以在需要這個類實例的時候來找到這個構造函數并把相關實例構造出來,以此來為被@Inject標記了的變量提供依賴;
@Module:@Module用于標注提供依賴的類。你可能會有點困惑,上面不是提到用@Inject標記構造函數就可以提供依賴了么,為什么還需要@Module?很多時候我們需要提供依賴的構造函數是第三方庫的,我們沒法給它加上@Inject注解,又比如說提供以來的構造函數是帶參數的,如果我們之所簡單的使用@Inject標記它,那么他的參數又怎么來呢?@Module正是幫我們解決這些問題的。
@Provides:@Provides用于標注Module所標注的類中的方法,該方法在需要提供依賴時被調用,從而把預先提供好的對象當做依賴給標注了@Inject的變量賦值;
@Component:@Component用于標注接口,是依賴需求方和依賴提供方之間的橋梁。被Component標注的接口在編譯時會生成該接口的實現類(如果@Component標注的接口為CarComponent,則編譯期生成的實現類為DaggerCarComponent),我們通過調用這個實現類的方法完成注入;
@Qulifier:@Qulifier用于自定義注解,也就是說@Qulifier就如同Java提供的幾種基本元注解一樣用來標記注解類。我們在使用@Module來標注提供依賴的方法時,方法名我們是可以隨便定義的(雖然我們定義方法名一般以provide開頭,但這并不是強制的,只是為了增加可讀性而已)。那么Dagger2怎么知道這個方法是為誰提供依賴呢?答案就是返回值的類型,Dagger2根據返回值的類型來決定為哪個被@Inject標記了的變量賦值。但是問題來了,一旦有多個一樣的返回類型Dagger2就懵逼了。@Qulifier的存在正式為了解決這個問題,我們使用@Qulifier來定義自己的注解,然后通過自定義的注解去標注提供依賴的方法和依賴需求方(也就是被@Inject標注的變量),這樣Dagger2就知道為誰提供依賴了。----一個更為精簡的定義:當類型不足以鑒別一個依賴的時候,我們就可以使用這個注解標示;
@Scope:@Scope同樣用于自定義注解,我能可以通過@Scope自定義的注解來限定注解作用域,實現局部的單例;
@Singleton:@Singleton其實就是一個通過@Scope定義的注解,我們一般通過它來實現全局單例。但實際上它并不能提前全局單例,是否能提供全局單例還要取決于對應的Component是否為一個全局對象。
我們提到@Inject和@Module都可以提供依賴,那如果我們即在構造函數上通過標記@Inject提供依賴,有通過@Module提供依賴Dagger2會如何選擇呢?具體規則如下:
步驟1:首先查找@Module標注的類中是否存在提供依賴的方法。
步驟2:若存在提供依賴的方法,查看該方法是否存在參數。
a:若存在參數,則按從步驟1開始依次初始化每個參數;
b:若不存在,則直接初始化該類實例,完成一次依賴注入。
步驟3:若不存在提供依賴的方法,則查找@Inject標注的構造函數,看構造函數是否存在參數。
a:若存在參數,則從步驟1開始依次初始化每一個參數
b:若不存在,則直接初始化該類實例,完成一次依賴注入。
Dagger2注解@Module ,@Component,@Inject的關系
簡單的說,就是一個工廠模式,由Dagger負責創建工廠,幫忙生產instance。遵從Java規范JSR 330,可以使用這些注解。現在不研究Dagger2是如何根據注解去生成工廠的,先來看看工廠是什么東西,理解為什么可以實現了DI(Dependency Injection),如何創建IoC(Inverse of Control)容器。
Dagger2是通過依賴注入完成類的初始化。
這個過程需要三部分:
#1****依賴提供方(生產者)
#2****依賴注入容器(橋梁)
#3****依賴需求方(消費者)
總結:
@Inject主要有兩個作用
#1作為依賴注提供方:
使用@Inject注解構造方法。
注解類的構造函數,讓Dagger2幫我們實例化該類,并注入。
#2作為依賴需求方:
使用@Inject注解成員。
如果一個成員變量被@Inject注解修飾,并且成員類的構造函數也被@Inject注解,那么dagger2幫我們實例化該成員類,并注入。
通常在需要依賴的地方使用這個注解。換句話說,你用它告訴Dagger這個類或者字段需要依賴注入。這樣,Dagger就會構造一個這個類的實例并滿足他們的依賴。
使用@Inject可以讓IoC容器負責生成instance,如果沒有這個注解,dagger將不認識,當做普通類,無法代理
@Module的作用
#1@Module注解類,負責管理依賴。
Module 其實是一個簡單工廠模式,Module 里面的方法都是創建相應類實例的方法。
#2通過@Module獲得第三方類庫的對象。
#3@Module是一個依賴提供方的合集。
@ModulepublicclassAModule{@ProvidespublicGsonprovideGson(){returnnewGson();}}
@Provides
#1注解@Module類中的方法。
在modules中,我們定義的方法是用這個注解,以此來告訴Dagger我們想要構造對象并提供這些依賴。
@Component的作用
#1@Component一般用來注解接口。
#2負責在@Inject和@Module之間建立連接。
也可以說是@Inject和@Module的橋梁,它的主要作用就是連接這兩個部分。
#3實例化@Inject注解的類時,遇到沒有構造函數的類依賴,則該依賴由@Module修飾的類提供。
#4****依賴注入容器只是一個接口interface。
Component需要引用到目標類的實例,Component會查找目標類中用Inject注解標注的屬性,查找到相應的屬性后會接著查找該屬性對應的用Inject標注的構造函數(這時候就發生聯系了),剩下的工作就是初始化該屬性的實例并把實例進行賦值。因此我們也可以給Component叫另外一個名字注入器(Injector)
Component注解的類,再編譯之后,會生產一個以Dagger+類名的一個類,如下面的MainComponent會生成類DaggerMainComponent(補充一點,Kotlinkapt編譯生成類的位置:\build\generated\source\kapt\debug),我們需要在目標類MainActivity中加入下面代碼
DaggerMainComponent.builder()
.build()
.inject(this)
DaggerMainComponent使用了建造者設計模式,inject方法是我們MainComponent中定義的,這樣目標類就和Component建立了聯系.Component會去遍歷使用@Inject注解的常量,然后去查找對應的類是否有@Inject注解的構造方法,如果沒有就會報異常.
@Component{modules={HeaterModule.class,PumperModule.class}}publicinterfaceMachineComponent{voidinject(CoffeeMachine machine);}
dagger中Component就是最頂級的入口,dagger為之生成了工廠類 DaggerMachineComponent,目標是構建CoffeeMachine, 在CoffeeMachine中使用了Injection,那么依賴要由工廠類來提供。工廠類是根據modules的參數來找依賴綁定的。