出來混遲早要還的,技術債Dagger2:基礎篇
前言
年前架構組的大佬們,分享了一個內容:如何讓App Bundle支援Dagger2。
PS:關於App Bundle暫時不是本篇內容要講的
會議就如何在App Bundle中高效的使用Dagger2展開了激烈的討論,xxx表示應加強團隊技術建設,規範Dagger2的使用...
我tm都沒用過Dagger2,我是誰?我在哪?我都在聽些什麼?
正文
一、為什麼需要依賴注入
個人覺得,開始一個新技術的學習。前提是弄清楚這個技術會為我們解決什麼樣的問題、痛點。
拿Dagger來說,它解決了什麼呢?
對於我們來說,隨著專案的越來越大,類爆炸,程式碼量激增的情況便會顯現出來。如果我們某些類與某些類直接存在依賴關係的話,我們就會發現大量的new,導致了我們的類與類之間 耦合 的極為嚴重,進而我們的維護也變得更加複雜。
此時,依賴注入的思想便逐步被提上章程。用 注入 的方式,替代原本需要內部new的方式。而Dagger就是依賴注入框架之一,因其編譯期生成程式碼(無效能損失),依賴關係程式碼透明的特點迅速走紅~~
OK,不扯別的了,開始。
二、基礎用法
2.1、入門註解
- @Module和@Provides:定義提供依賴關係的類和方法
- @Inject:需要的依賴。可以在建構函式,欄位或方法上使用
- @Component:用於構建將所有依賴關係連線在一起的介面
2.2、入門demo
Dagger2開始前,我們想用一個普通的demo。背景來自於《權利的遊戲》,程式碼來源: Dagger 2 for Android Beginners — Dagger 2 part1
PS:訪問此網站需要科學上網...
public class BattleOfBastards { public static void main(String[] args){ // 關於IronBank、Allies、Starks、Boltons因為篇幅原因就不展開了,就是普通的類而已。 IronBank bank = new IronBank(); Allies allies = new Allies(bank); Starks starks = new Starks(allies, bank); Boltons boltons = new Boltons(allies, bank); War war = new War(starks, boltons); war.prepare(); war.report(); } }
我們可以看到,想要執行這個demo,我們需要new出很多需要的類。可見當專案逐漸增大,這個類的維護難度可想而知。
接下來我們看一下用Dagger2進行改造:
public class Starks implements House { @Inject public Starks(){} @Override public void prepareForWar() { System.out.println(this.getClass().getSimpleName()+" prepared for war"); } @Override public void reportForWar() { System.out.println(this.getClass().getSimpleName()+" reporting.."); } } // Boltons類同上 public class War { private Starks starks; private Boltons boltons; @Inject public War(Starks starks, Boltons bolton){ this.starks = starks; this.boltons = bolton; } public void prepare(){ starks.prepareForWar(); boltons.prepareForWar(); } public void report(){ starks.reportForWar(); boltons.reportForWar(); } } @Component interface BattleComponent { War getWar(); }
此時我們需要build一下,我們需要通過編譯,讓Dagger2幫我們生成我們所需要的類: BattleComponent ,編譯成功,我們就可以改造 main()
啦。
public class BattleOfBastards { public static void main(String[] args){ BattleComponent component = DaggerBattleComponent.create(); War war = component.getWar(); war.prepare(); war.report(); } }
很明顯能夠看出來,BattleComponent為我們承受了成噸的傷害。那麼就讓我們看一看BattleComponent的原始碼:
public final class DaggerBattleComponent implements BattleComponent { private DaggerBattleComponent(Builder builder) {} public static Builder builder() { return new Builder(); } public static BattleComponent create() { return new Builder().build(); } @Override public War getWar() { return new War(new Starks(), new Boltons()); } public static final class Builder { private Builder() {} public BattleComponent build() { return new DaggerBattleComponent(this); } } }
這裡我們可以看到,它實現了 BattleComponent
介面,並且getWar()是通過new War()的方式提供War的例項,這是因為,我們@Inject了War的構造方法, new Starks(), new Boltons()
也是同樣的道理。
2.3、@Module和@Provide註釋
這裡我們因為需要獲取 War
的例項,在@Component了一個 getWar()
方法。那一定會有朋友問,如果我想外部獲取 Starks
,是不是也可以定義一個 getStarks()
?那還用問嗎,當然可以!
@Component interface BattleComponent { War getWar(); Starks getStarks(); Boltons getBoltons(); }
Starks
、 Boltons
中的@Inject不變。這時我們重新Build一下,再看一看DaggerBattleComponent:
public final class DaggerBattleComponent implements BattleComponent { // 省略相同內容 @Override public War getWar() { return new War(getStarks(), getBoltons()); } @Override public Starks getStarks() { return new Starks(); } @Override public Boltons getBoltons() { return new Boltons(); } // 省略相同內容 }
這裡自動實現的 getStarks()
是採用了 new Starks();
的方式,為什麼是new,是因為我們@Inject註解到了 Starks
的構造方法上了。
如果這裡我們沒有@Inject會發生什麼呢?
不用試了,最終會提示我們:需要提供@Inject或@Provides介面。既然提到了@Provides,那就讓我們看一看它,不過說它之前,我們需要先看一看@Module:
@Module
@Module註釋標記模組/類。例如,在Android中。我們可以有一個呼叫的模組ContextModule,該模組可以為其他類提供ApplicationContext和Context依賴。因此,我們需要將類標記ContextModule用@Module。
不理解,不要緊。一會結合程式碼再回過頭就明白了。
@Provides
@Provides註釋標記Module內部的方法,為外部提供了獲取依賴的方式。例如,在上面提到的Android示例中,我們ContextModule使用@Module標記,我們需要使用@Provides標記提供ApplicationContext和Context例項的方法。
不理解,不要緊。一會結合程式碼再回過頭就明白了。
接下來讓我們把這倆個註解的內容也加入到 BattleOfBastards
之中:
先引入倆個全新的角色:錢和士兵
public class Cash { public Cash(){//do something} } public class Soldiers { public Soldiers(){//do something} }
然後我們構造一個Module,用它來管理我們的錢和士兵:
@Module public class BraavosModule { Cash cash; Soldiers soldiers; public BraavosModule(Cash cash, Soldiers soldiers){ this.cash=cash; this.soldiers=soldiers; } @Provides Cash provideCash(){ return cash; } @Provides Soldiers provideSoldiers(){ return soldiers; } }
這裡@Module為我們增加了更多的依賴性,怎麼讓它生效呢?這樣既可:
@Component(modules = BraavosModule.class) interface BattleComponent { War getWar(); Cash getCash(); Soldiers getSoldiers(); }
然後我們可以這樣使用我們的 Cash
和 Soldiers
。
public class BattleOfBastards { public static void main(String[] args){ BattleComponent component = DaggerBattleComponent .builder().braavosModule(new BraavosModule(new Cash(), new Soldiers())).build(); War war = component.getWar(); war.prepare(); war.report(); component.getCash(); component.getSoldiers(); } }
我們 component.getCash();
所得到的Cash例項,是 BraavosModule
例項中provideCash()所提供的。也就是我們@Provides也註解的內容。不過@Provides是如何生成程式碼的?讓我們一同看一下:
public final class DaggerBattleComponent implements BattleComponent { // 省略部分程式碼 @Override public Cash getCash() { return BraavosModule_ProvideCashFactory.proxyProvideCash(braavosModule); } } // BraavosModule_ProvideCashFactory public final class BraavosModule_ProvideCashFactory implements Factory<Cash> { private final BraavosModule module; public BraavosModule_ProvideCashFactory(BraavosModule module) { this.module = module; } // 省略部分無用內容 public static Cash proxyProvideCash(BraavosModule instance) { return Preconditions.checkNotNull( instance.provideCash(), "Cannot return null from a non-@Nullable @Provides method"); } }
從上述生成的程式碼可以看到,我們呼叫 getCash()
時,實際上是呼叫了 BraavosModule_ProvideCashFactory
例項的 proxyProvideCash(BraavosModule instance)
,這個方法最終呼叫 instance.provideCash()
返回我們@Provide的內容。
尾聲
沒想到光寫基礎的內容,就已經涉及到了這麼多的內容。既然如果那麼本篇內容就先止步於此,關於Android篇的內容,就放在下一篇文章中吧!
我是一個應屆生,最近和朋友們維護了一個公眾號,內容是我們在從應屆生過渡到開發這一路所踩過的坑,以及我們一步步學習的記錄,如果感興趣的朋友可以關注一下,一同加油~

個人公眾號:鹹魚正翻身