出來混遲早要還的,技術債Dagger2:基礎篇

鹹魚正翻身發表於2019-02-10

前言

年前架構組的大佬們,分享了一個內容:如何讓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();
}
複製程式碼

StarksBoltons中的@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();
}
複製程式碼

然後我們可以這樣使用我們的CashSoldiers

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篇的內容,就放在下一篇文章中吧!

我是一個應屆生,最近和朋友們維護了一個公眾號,內容是我們在從應屆生過渡到開發這一路所踩過的坑,以及我們一步步學習的記錄,如果感興趣的朋友可以關注一下,一同加油~

個人公眾號:鹹魚正翻身

相關文章