前言
年前架構組的大佬們,分享了一個內容:如何讓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篇的內容,就放在下一篇文章中吧!