為什么使用Dagger2
無(wú)論是構(gòu)造函數(shù)注入還是接口注入,都避免不了要編寫大量的模板代碼培漏。機(jī)智的猿猿們當(dāng)然不開(kāi)心做這些重復(fù)性的工作溪厘,于是各種依賴注入框架應(yīng)用而生胡本。但是這么多的依賴注入框架為什么我們卻偏愛(ài)Dagger2呢牌柄?我們先從Spring中的控制反轉(zhuǎn)(IOC)說(shuō)起。
談起依賴注入侧甫,做過(guò)J2EE開(kāi)發(fā)的同學(xué)一定會(huì)想起Spring IOC珊佣,那通過(guò)迷之XML來(lái)配置依賴的方式真的很讓人討厭;而且XML與Java代碼分離也導(dǎo)致代碼鏈難以追蹤披粟。之后更加先進(jìn)的Guice(Android端也有個(gè)RoboGuice)出現(xiàn)了咒锻,我們不再需要通過(guò)XML來(lái)配置依賴,但其運(yùn)行時(shí)實(shí)現(xiàn)注入的方式讓我們?cè)谧粉櫤投ㄎ诲e(cuò)誤的時(shí)候卻又萬(wàn)分痛苦守屉。開(kāi)篇提到過(guò)Dagger就是受Guice的啟發(fā)而開(kāi)發(fā)出來(lái)的惑艇;Dagger繼承了前輩的思想,在性能又碾壓了它的前輩Guice拇泛,可謂是長(zhǎng)江后浪推前浪滨巴,前浪死在沙灘上。
又如開(kāi)篇我在簡(jiǎn)介中說(shuō)到的俺叭,Dagger是一種半靜態(tài)半運(yùn)行時(shí)的DI框架恭取,雖說(shuō)依賴注入是完全靜態(tài)的,但是生成有向無(wú)環(huán)圖(DAG)還是基于反射來(lái)實(shí)現(xiàn)熄守,這無(wú)論在大型的服務(wù)端應(yīng)用還是在Android應(yīng)用上都不是最優(yōu)方案蜈垮。升級(jí)版的Dagger2解決了這一問(wèn)題,從半靜態(tài)變?yōu)橥耆o態(tài)裕照,從Map式的API變成申明式API(@Module)攒发,生成的代碼更優(yōu)雅高效;而且一旦出錯(cuò)我們?cè)诰幾g期間就能發(fā)現(xiàn)晋南。所以Dagger2對(duì)開(kāi)發(fā)者的更加友好了惠猿,當(dāng)然Dagger2也因此喪失了一些靈活性,但總體來(lái)說(shuō)利還是遠(yuǎn)遠(yuǎn)大于弊的搬俊。
前面提到這種A B C D E連續(xù)依賴的問(wèn)題紊扬,一旦E的創(chuàng)建方式發(fā)生了改變就會(huì)引發(fā)連鎖反應(yīng)蜒茄,可能會(huì)導(dǎo)致A B C D都需要做針對(duì)性的修改;但是騷年餐屎,你以為為這僅僅是工作量的問(wèn)題嗎檀葛?更可怕的是我們創(chuàng)建A時(shí)需要按順序先創(chuàng)建E D C B四個(gè)對(duì)象,而且必須保證順序上是正確的腹缩。Dagger2就很好的解決了這一問(wèn)題(不只是Dagger2屿聋,在其他DI框架中開(kāi)發(fā)者同樣不需要關(guān)注這些問(wèn)題)。
Dagger2注解
開(kāi)篇我們就提到Dagger2是基于Java注解來(lái)實(shí)現(xiàn)依賴注入的藏鹊,那么在正式使用之前我們需要先了解下Dagger2中的注解润讥。Dagger2使用過(guò)程中我們通常接觸到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。
@Inject:@Inject有兩個(gè)作用盘寡,一是用來(lái)標(biāo)記需要依賴的變量楚殿,以此告訴Dagger2為它提供依賴;二是用來(lái)標(biāo)記構(gòu)造函數(shù)竿痰,Dagger2通過(guò)@Inject注解可以在需要這個(gè)類實(shí)例的時(shí)候來(lái)找到這個(gè)構(gòu)造函數(shù)并把相關(guān)實(shí)例構(gòu)造出來(lái)脆粥,以此來(lái)為被@Inject標(biāo)記了的變量提供依賴;
@Module:@Module用于標(biāo)注提供依賴的類影涉。你可能會(huì)有點(diǎn)困惑变隔,上面不是提到用@Inject標(biāo)記構(gòu)造函數(shù)就可以提供依賴了么,為什么還需要@Module蟹倾?很多時(shí)候我們需要提供依賴的構(gòu)造函數(shù)是第三方庫(kù)的匣缘,我們沒(méi)法給它加上@Inject注解,又比如說(shuō)提供以來(lái)的構(gòu)造函數(shù)是帶參數(shù)的鲜棠,如果我們之所簡(jiǎn)單的使用@Inject標(biāo)記它肌厨,那么他的參數(shù)又怎么來(lái)呢?@Module正是幫我們解決這些問(wèn)題的岔留。
@Provides:@Provides用于標(biāo)注Module所標(biāo)注的類中的方法夏哭,該方法在需要提供依賴時(shí)被調(diào)用,從而把預(yù)先提供好的對(duì)象當(dāng)做依賴給標(biāo)注了@Inject的變量賦值献联;
@Component:@Component用于標(biāo)注接口竖配,是依賴需求方和依賴提供方之間的橋梁。被Component標(biāo)注的接口在編譯時(shí)會(huì)生成該接口的實(shí)現(xiàn)類(如果@Component標(biāo)注的接口為CarComponent里逆,則編譯期生成的實(shí)現(xiàn)類為DaggerCarComponent),我們通過(guò)調(diào)用這個(gè)實(shí)現(xiàn)類的方法完成注入进胯;
@Qulifier:@Qulifier用于自定義注解,也就是說(shuō)@Qulifier就如同Java提供的幾種基本元注解一樣用來(lái)標(biāo)記注解類原押。我們?cè)谑褂聾Module來(lái)標(biāo)注提供依賴的方法時(shí)胁镐,方法名我們是可以隨便定義的(雖然我們定義方法名一般以provide開(kāi)頭,但這并不是強(qiáng)制的,只是為了增加可讀性而已)盯漂。那么Dagger2怎么知道這個(gè)方法是為誰(shuí)提供依賴呢颇玷?答案就是返回值的類型,Dagger2根據(jù)返回值的類型來(lái)決定為哪個(gè)被@Inject標(biāo)記了的變量賦值就缆。但是問(wèn)題來(lái)了帖渠,一旦有多個(gè)一樣的返回類型Dagger2就懵逼了。@Qulifier的存在正式為了解決這個(gè)問(wèn)題竭宰,我們使用@Qulifier來(lái)定義自己的注解空郊,然后通過(guò)自定義的注解去標(biāo)注提供依賴的方法和依賴需求方(也就是被@Inject標(biāo)注的變量),這樣Dagger2就知道為誰(shuí)提供依賴了切揭。----一個(gè)更為精簡(jiǎn)的定義:當(dāng)類型不足以鑒別一個(gè)依賴的時(shí)候狞甚,我們就可以使用這個(gè)注解標(biāo)示;
@Scope:@Scope同樣用于自定義注解廓旬,我能可以通過(guò)@Scope自定義的注解來(lái)限定注解作用域哼审,實(shí)現(xiàn)局部的單例;
@Singleton:@Singleton其實(shí)就是一個(gè)通過(guò)@Scope定義的注解嗤谚,我們一般通過(guò)它來(lái)實(shí)現(xiàn)全局單例棺蛛。但實(shí)際上它并不能提前全局單例,是否能提供全局單例還要取決于對(duì)應(yīng)的Component是否為一個(gè)全局對(duì)象巩步。
我們提到@Inject和@Module都可以提供依賴,那如果我們即在構(gòu)造函數(shù)上通過(guò)標(biāo)記@Inject提供依賴桦踊,有通過(guò)@Module提供依賴Dagger2會(huì)如何選擇呢椅野?具體規(guī)則如下:
步驟1:首先查找@Module標(biāo)注的類中是否存在提供依賴的方法。
步驟2:若存在提供依賴的方法籍胯,查看該方法是否存在參數(shù)竟闪。
a:若存在參數(shù),則按從步驟1開(kāi)始依次初始化每個(gè)參數(shù)杖狼;
b:若不存在炼蛤,則直接初始化該類實(shí)例,完成一次依賴注入蝶涩。
步驟3:若不存在提供依賴的方法理朋,則查找@Inject標(biāo)注的構(gòu)造函數(shù),看構(gòu)造函數(shù)是否存在參數(shù)绿聘。
a:若存在參數(shù)嗽上,則從步驟1開(kāi)始依次初始化每一個(gè)參數(shù)
b:若不存在,則直接初始化該類實(shí)例熄攘,完成一次依賴注入兽愤。
Dagger2注解@Module ,@Component,@Inject的關(guān)系
簡(jiǎn)單的說(shuō)浅萧,就是一個(gè)工廠模式逐沙,由Dagger負(fù)責(zé)創(chuàng)建工廠,幫忙生產(chǎn)instance洼畅。遵從Java規(guī)范JSR 330酱吝,可以使用這些注解。現(xiàn)在不研究Dagger2是如何根據(jù)注解去生成工廠的土思,先來(lái)看看工廠是什么東西务热,理解為什么可以實(shí)現(xiàn)了DI(Dependency Injection),如何創(chuàng)建IoC(Inverse of Control)容器己儒。
Dagger2是通過(guò)依賴注入完成類的初始化崎岂。
這個(gè)過(guò)程需要三部分:
#1****依賴提供方(生產(chǎn)者)
#2****依賴注入容器(橋梁)
#3****依賴需求方(消費(fèi)者)
總結(jié):
@Inject主要有兩個(gè)作用
#1作為依賴注提供方:
使用@Inject注解構(gòu)造方法。
注解類的構(gòu)造函數(shù)闪湾,讓Dagger2幫我們實(shí)例化該類冲甘,并注入。
#2作為依賴需求方:
使用@Inject注解成員途样。
如果一個(gè)成員變量被@Inject注解修飾江醇,并且成員類的構(gòu)造函數(shù)也被@Inject注解,那么dagger2幫我們實(shí)例化該成員類何暇,并注入陶夜。
通常在需要依賴的地方使用這個(gè)注解。換句話說(shuō)裆站,你用它告訴Dagger這個(gè)類或者字段需要依賴注入条辟。這樣,Dagger就會(huì)構(gòu)造一個(gè)這個(gè)類的實(shí)例并滿足他們的依賴宏胯。
使用@Inject可以讓IoC容器負(fù)責(zé)生成instance羽嫡,如果沒(méi)有這個(gè)注解,dagger將不認(rèn)識(shí)肩袍,當(dāng)做普通類杭棵,無(wú)法代理
@Module的作用
#1@Module注解類,負(fù)責(zé)管理依賴氛赐。
Module 其實(shí)是一個(gè)簡(jiǎn)單工廠模式魂爪,Module 里面的方法都是創(chuàng)建相應(yīng)類實(shí)例的方法。
#2通過(guò)@Module獲得第三方類庫(kù)的對(duì)象鹰祸。
#3@Module是一個(gè)依賴提供方的合集甫窟。
@ModulepublicclassAModule{@ProvidespublicGsonprovideGson(){returnnewGson();}}
@Provides
#1注解@Module類中的方法。
在modules中蛙婴,我們定義的方法是用這個(gè)注解粗井,以此來(lái)告訴Dagger我們想要構(gòu)造對(duì)象并提供這些依賴。
@Component的作用
#1@Component一般用來(lái)注解接口。
#2負(fù)責(zé)在@Inject和@Module之間建立連接浇衬。
也可以說(shuō)是@Inject和@Module的橋梁懒构,它的主要作用就是連接這兩個(gè)部分。
#3實(shí)例化@Inject注解的類時(shí)耘擂,遇到?jīng)]有構(gòu)造函數(shù)的類依賴胆剧,則該依賴由@Module修飾的類提供。
#4****依賴注入容器只是一個(gè)接口interface醉冤。
Component需要引用到目標(biāo)類的實(shí)例歌馍,Component會(huì)查找目標(biāo)類中用Inject注解標(biāo)注的屬性汞舱,查找到相應(yīng)的屬性后會(huì)接著查找該屬性對(duì)應(yīng)的用Inject標(biāo)注的構(gòu)造函數(shù)(這時(shí)候就發(fā)生聯(lián)系了)矮冬,剩下的工作就是初始化該屬性的實(shí)例并把實(shí)例進(jìn)行賦值宝穗。因此我們也可以給Component叫另外一個(gè)名字注入器(Injector)
Component注解的類,再編譯之后,會(huì)生產(chǎn)一個(gè)以Dagger+類名的一個(gè)類,如下面的MainComponent會(huì)生成類DaggerMainComponent(補(bǔ)充一點(diǎn),Kotlinkapt編譯生成類的位置:\build\generated\source\kapt\debug),我們需要在目標(biāo)類MainActivity中加入下面代碼
DaggerMainComponent.builder()
.build()
.inject(this)
DaggerMainComponent使用了建造者設(shè)計(jì)模式,inject方法是我們MainComponent中定義的,這樣目標(biāo)類就和Component建立了聯(lián)系.Component會(huì)去遍歷使用@Inject注解的常量,然后去查找對(duì)應(yīng)的類是否有@Inject注解的構(gòu)造方法,如果沒(méi)有就會(huì)報(bào)異常.
@Component{modules={HeaterModule.class,PumperModule.class}}publicinterfaceMachineComponent{voidinject(CoffeeMachine machine);}
dagger中Component就是最頂級(jí)的入口,dagger為之生成了工廠類 DaggerMachineComponent螺捐,目標(biāo)是構(gòu)建CoffeeMachine颠悬, 在CoffeeMachine中使用了Injection,那么依賴要由工廠類來(lái)提供定血。工廠類是根據(jù)modules的參數(shù)來(lái)找依賴綁定的赔癌。