「神兵利器Dagger2 | 掘金技術徵文 」

Dagger-匕首,鼎鼎大名的Square公司旗下又一把利刃(沒錯!還有一把黃油刀,喚做ButterKnife);故此給本篇取名神兵利器Dagger2。java

Dagger2起源於Dagger,是一款基於Java註解來實現的徹底在編譯階段完成依賴注入的開源庫,主要用於模塊間解耦、提升代碼的健壯性和可維護性。Dagger2在編譯階段經過apt利用Java註解自動生成Java代碼,而後結合手寫的代碼來自動幫咱們完成依賴注入的工做。git

起初Square公司受到Guice的啓發而開發了Dagger,可是Dagger這種半靜態半運行時的框架仍是有些性能問題(雖然說依賴注入是徹底靜態的,可是其有向無環圖(Directed Acyclic Graph)仍是基於反射來生成的,這不管在大型的服務端應用仍是在Android應用上都不是最優方案)。所以Google工程師Fork了Dagger項目,對它進行了改造。因而變演變出了今天咱們要討論的Dagger2,因此說Dagger2其實就是高配版的Dagger。github

依賴注入(Dependency Injection)

那麼什麼是依賴注入呢?在解釋這個概念前咱們先看一小段代碼:架構

public class Car{

    private Engine engine;

    public Car(){
        engine = new Engine();
    }
}複製代碼

這段Java代碼中Car類持有了對Engine實例的引用,咱們稱之爲Car類對Engine類有一個依賴。而依賴注入則是指經過注入的方式實現類與類之間的依賴,下面是常見的三種依賴注入的方式:框架

一、構造注入:經過構造函數傳參給依賴的成員變量賦值,從而實現注入。

public class Car{

    private Engine engine;

    public Car(Engine engine){
        this.engine = engine;
    }
}複製代碼

二、接口注入:實現接口方法,一樣以傳參的方式實現注入。

public interface Injection<T>{

    void inject(T t);
}

public class Car implements Injection<Engine>{

    private Engine engine;

    public Car(){}

    public void inject(Engine engine){
        this.engine = engine;
    }

}複製代碼

三、註解注入:使用Java註解在編譯階段生成代碼實現注入或者是在運行階段經過反射實現注入。

public class Car{

    @Inject
    Engine engine;

    public Car(){}
}複製代碼

前兩種注入方式須要咱們編寫大量的模板代碼,而機智的Dagger2則是經過Java註解在編譯期來實現依賴注入的。ide

爲何須要依賴注入

咱們之所是要依賴注入,最重要的就是爲了解耦,達到高內聚低耦合的目的,保證代碼的健壯性、靈活性和可維護性。函數

下面咱們看看同一個業務的兩種實現方案:post

一、方案A

public class Car{

    private Engine engine;
    private List<Wheel> wheels;

    public Car(){
        engine = new Engine();
        wheels = new ArrayList<>();
        for(int i = 0; i < 4; i++){
            wheels.add(new Wheel());
        }
    }

    public void start{
        System.out.println("啓動汽車");
    }
}

public class CarTest{

    public static void main(String[] args){
        Car car = new Car();
        car.start();
    }
}複製代碼

二、方案B

public class Car{

    private Engine engine;
    private List<Wheel> wheels;

    public Car(Engine engine, List<Wheel> wheels){
        this.engine = engine;
        this.wheels = wheels;
    }

    public void start{
        System.out.println("啓動汽車");
    }
}

public class CarTest{

    public static void main(String[] args){
        Engine engine = new Engine();
        List<Wheel> wheels = new ArrayList<>();
        for(int i = 0; i < 4; i++){
            wheels.add(new Wheel());
        }
        Car car = new Car(engine, wheels);
        car.start();
    }
}複製代碼

方案A:因爲沒有依賴注入,所以須要咱們本身是在Car的構造函數中建立Engine和Wheel對象。性能

方案B:咱們手動以構造函數的方式注入依賴,將engine和wheels做爲參數傳入而不是在Car的構造函數中去顯示的建立。測試

方案A明顯喪失了靈活性,一切依賴都是在Car類的內部建立,Car與Engine和Wheel嚴重耦合。一旦Engine或者Wheel的建立方式發生了改變,咱們就必需要去修改Car類的構造函數(好比說如今建立Wheel實例的構造函數改變了,須要傳入Rubber(橡膠)了);另外咱們也沒辦法替換動態的替換依賴實例(好比咱們想把Car的Wheel(輪胎)從鄧祿普(輪胎品牌)換成米其林(輪胎品牌)的)。這類問題在大型的商業項目中則更加嚴重,每每A依賴B、B依賴C、C依賴D、D依賴E;一旦稍有改動便牽一髮而動全身,想一想均可怕!而依賴注入則很好的幫咱們解決了這一問題。

爲何是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使用入門

前面長篇大論的基本都在介紹概念,下面咱們看看Dagger2的基本應用。關於Dagger2的依賴配置就不在這裏佔用篇幅去描述了,你們能夠到它的github主頁下去查看官方教程github.com/google/dagg…。接下來咱們仍是拿前面的Car和Engine來舉例。

一、案例A

Car類是需求依賴方,依賴了Engine類;所以咱們須要在類變量Engine上添加@Inject來告訴Dagger2來爲本身提供依賴。

public class Car {

    @Inject
    Engine engine;

    public Car() {
        DaggerCarComponent.builder().build().inject(this);
    }

    public Engine getEngine() {
        return this.engine;
    }
}複製代碼

Engine類是依賴提供方,所以咱們須要在它的構造函數上添加@Inject

public class Engine {

    @Inject
    Engine(){}

    public void run(){
        System.out.println("引擎轉起來了~~~");
    }
}複製代碼

接下來咱們須要建立一個用@Component標註的接口CarComponent,這個CarComponent其實就是一個注入器,這裏用來將Engine注入到Car中。

@Component
public interface CarComponent {
    void inject(Car car);
}複製代碼

完成這些以後咱們須要Build下項目,讓Dagger2幫咱們生成相關的Java類。接着咱們就能夠在Car的構造函數中調用Dagger2生成的DaggerCarComponent來實現注入(這其實在前面Car類的代碼中已經有了體現)

public Car() {
    DaggerCarComponent.builder().build().inject(this);
}複製代碼

二、案例B

若是建立Engine的構造函數是帶參數的呢?好比說製造一臺引擎是須要齒輪(Gear)的。或者Eggine類是咱們沒法修改的呢?這時候就須要@Module和@Provide上場了。

一樣咱們須要在Car類的成員變量Engine上加上@Inject表示本身須要Dagger2爲本身提供依賴;Engine類的構造函數上的@Inject也須要去掉,應爲如今不須要經過構造函數上的@Inject來提供依賴了。

public class Car {

    @Inject
    Engine engine;

    public Car() {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }

    public Engine getEngine() {
        return this.engine;
    }
}複製代碼

接着咱們須要一個Module類來生成依賴對象。前面介紹的@Module就是用來標準這個類的,而@Provide則是用來標註具體提供依賴對象的方法(這裏有個不成文的規定,被@Provide標註的方法命名咱們通常以provide開頭,這並非強制的但有益於提高代碼的可讀性)。

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    @Provides Engine provideEngine(){
        return new Engine("gear");
    }
}複製代碼

接下來咱們還須要對CarComponent進行一點點修改,以前的@Component註解是不帶參數的,如今咱們須要加上modules = {MarkCarModule.class},用來告訴Dagger2提供依賴的是MarkCarModule這個類。

@Component(modules = {MarkCarModule.class})
public interface CarComponent {
    void inject(Car car);
}複製代碼

Car類的構造函數咱們也須要修改,相比以前多了個markCarModule(new MarkCarModule())方法,這就至關於告訴了注入器DaggerCarComponentMarkCarModule提供的依賴注入到了Car類中。

public Car() {
   DaggerCarComponent.builder()
           .markCarModule(new MarkCarModule())
           .build().inject(this);
}複製代碼

這樣一個最最基本的依賴注入就完成了,接下來咱們測試下咱們的代碼。

public static void main(String[] args){
    Car car = new Car();
    car.getEngine().run();
}複製代碼

輸出

引擎轉起來了~~~複製代碼

三、案例C

那麼若是一臺汽車有兩個引擎(也就是說Car類中有兩個Engine變量)怎麼辦呢?不要緊,咱們還有@Qulifier!首先咱們須要使用Qulifier定義兩個註解:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierA { }複製代碼
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierB { }複製代碼

同時咱們須要對依賴提供方作出修改

@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    @QualifierA
    @Provides
    Engine provideEngineA(){
        return new Engine("gearA");
    }

    @QualifierB
    @Provides
    Engine provideEngineB(){
        return new Engine("gearB");
    }
}複製代碼

接下來依賴需求方Car類一樣須要修改

public class Car {

    @QualifierA @Inject Engine engineA;
    @QualifierB @Inject Engine engineB;

    public Car() {
        DaggerCarComponent.builder().markCarModule(new MarkCarModule())
                .build().inject(this);
    }

    public Engine getEngineA() {
        return this.engineA;
    }

    public Engine getEngineB() {
        return this.engineB;
    }
}複製代碼

最後咱們再對Engine類作些調整方便測試

public class Engine {

    private String gear;

    public Engine(String gear){
        this.gear = gear;
    }

    public void printGearName(){
        System.out.println("GearName:" + gear);
    }
}複製代碼

測試代碼

public static void main(String[] args) {
    Car car = new Car();
    car.getEngineA().printGearName();
    car.getEngineB().printGearName();
}複製代碼

執行結果:

GearName:gearA
GearName:gearB複製代碼

四、案例D

接下來咱們看看@Scope是如何限定做用域,實現局部單例的。

首先咱們須要經過@Scope定義一個CarScope註解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface CarScope {
}複製代碼

接着咱們須要用這個@CarScope去標記依賴提供方MarkCarModule。

@Module
public class MarkCarModule {

    public MarkCarModule() {
    }

    @Provides
    @CarScope
    Engine provideEngine() {
        return new Engine("gear");
    }
}複製代碼

同時還須要使用@Scope去標註注入器Compoent

@CarScope
@Component(modules = {MarkCarModule.class})
public interface CarComponent {
    void inject(Car car);
}複製代碼

爲了便於測試咱們對Car和Engine類作了一些改造:

public class Car {

    @Inject Engine engineA;
    @Inject Engine engineB;

    public Car() {
        DaggerCarComponent.builder()
                .markCarModule(new MarkCarModule())
                .build().inject(this);
    }
}複製代碼
public class Engine {

    private String gear;

    public Engine(String gear){
        System.out.println("Create Engine");
        this.gear = gear;
    }
}複製代碼

若是咱們不適用@Scope,上面的代碼會實例化兩次Engine類,所以會有兩次\"Create Engine\"輸出。如今咱們在有@Scope的狀況測試下勞動成果:

public static void main(String[] args) {
    Car car = new Car();
    System.out.println(car.engineA.hashCode());
    System.out.println(car.engineB.hashCode());
}複製代碼

輸出

Create Engine複製代碼

bingo!咱們確實經過@Scope實現了局部的單例。

Dagger2原理分析

前面囉裏囉嗦的介紹了Dagger2的基本使用,接下來咱們再分析分析實現原理。這裏不會分析Dagger2根據註解生成各類代碼的原理,關於Java註解之後有機會再寫一篇文章來介紹。後面主要分析的是Dagger2生成的各類類如何幫咱們實現依賴注入,爲了便於理解我這裏選了前面相對簡單的案例B來作分析。

Dagger2編譯期生成的代碼位於build/generated/source/apt/debug/your package name/下面:

首先咱們看看Dagger2依據依賴提供方MarkCarModule生成的對應工廠類MarkCarModule_ProvideEngineFactory。爲了方便你們理解對比,後面我一概會把本身寫的類和Dagger2生成的類一併放出來。

/** * 咱們本身的類 */
@Module
public class MarkCarModule {

    public MarkCarModule(){ }

    @Provides Engine provideEngine(){
        return new Engine("gear");
    }
}

/** * Dagger2生成的工廠類 */
public final class MarkCarModule_ProvideEngineFactory implements Factory<Engine> {
  private final MarkCarModule module;

  public MarkCarModule_ProvideEngineFactory(MarkCarModule module) {
    assert module != null;
    this.module = module;
  }

  @Override
  public Engine get() {
    return Preconditions.checkNotNull(
        module.provideEngine(), "Cannot return null from a non-@Nullable @Provides method");
  }

  public static Factory<Engine> create(MarkCarModule module) {
    return new MarkCarModule_ProvideEngineFactory(module);
  }

  /** Proxies {@link MarkCarModule#provideEngine()}. */
  public static Engine proxyProvideEngine(MarkCarModule instance) {
    return instance.provideEngine();
  }
}複製代碼

咱們能夠看到MarkCarModule_ProvideEngineFactory中的get()調用了MarkCarModuleprovideEngine()方法來獲取咱們須要的依賴EngineMarkCarModule_ProvideEngineFactory的實例化有crate()建立,而且MarkCarModule的實例也是經過create()方法傳進來的。那麼這個create()必定會在哪裏調用的,咱們接着往下看。

前面提到@Component是依賴提供方(MarkCarModule)和依賴需求方(Car)以前的橋樑,那我看看Dagger2是如何經過CarComponent將二者聯繫起來的。

/** * 咱們本身的類 */
@Component(modules = {MarkCarModule.class})
public interface CarComponent {

    void inject(Car car);
}

/** * Dagger2生成的CarComponent實現類 */
public final class DaggerCarComponent implements CarComponent {
  private Provider<Engine> provideEngineProvider;

  private MembersInjector<Car> carMembersInjector;

  private DaggerCarComponent(Builder builder) {
    assert builder != null;
    initialize(builder);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static CarComponent create() {
    return builder().build();
  }

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {

    this.provideEngineProvider = MarkCarModule_ProvideEngineFactory.create(builder.markCarModule);
    this.carMembersInjector = Car_MembersInjector.create(provideEngineProvider);
  }

  @Override
  public void inject(Car car) {
    carMembersInjector.injectMembers(car);
  }

  public static final class Builder {
    private MarkCarModule markCarModule;

    private Builder() {}

    public CarComponent build() {
      if (markCarModule == null) {
        this.markCarModule = new MarkCarModule();
      }
      return new DaggerCarComponent(this);
    }

    public Builder markCarModule(MarkCarModule markCarModule) {
      this.markCarModule = Preconditions.checkNotNull(markCarModule);
      return this;
    }
  }
}複製代碼

經過上面的代碼咱們看到Dagger2依據CarComponent接口生成了實現類DaggerCarComponent(沒錯這正是咱們在Car的構造函數中使用DaggerCarComponent)。DaggerCarComponent在build的時候實例化了DaggerCarComponent對象,並首先調用MarkCarModule_ProvideEngineFactory.create(builder.markCarModule)始化了provideEngineProvider變量,接着調用Car_MembersInjector.create(provideEngineProvider)初始化了carMembersInjector變量。當咱們手動在Car類的構造函數中調用inject(Car car)方法時會執行carMembersInjector.injectMembers(car)。因此接下來咱們要看看Car_MembersInjector的實現。

public final class Car_MembersInjector implements MembersInjector<Car> {
  private final Provider<Engine> engineProvider;

  public Car_MembersInjector(Provider<Engine> engineProvider) {
    assert engineProvider != null;
    this.engineProvider = engineProvider;
  }

  public static MembersInjector<Car> create(Provider<Engine> engineProvider) {
    return new Car_MembersInjector(engineProvider);
  }

  @Override
  public void injectMembers(Car instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.engine = engineProvider.get();
  }

  public static void injectEngine(Car instance, Provider<Engine> engineProvider) {
    instance.engine = engineProvider.get();
  }
}複製代碼

Car_MembersInjector中的create()用於實例化本身,這個方法前面咱們看到是在DaggerCarComponent中調用的。injectMembers(Car instance)engineProvider.get()的返回值賦給了依賴需求方Car的engine變量,而engineProvider.get()正是本節一開始咱們提到的MarkCarModule_ProvideEngineFactory中的get()方法。至此整個依賴注入的流程就完成了。更復雜的應用場景會生成更加複雜的代碼,但原理都和前面分析的大同小異。

總結

這篇文章只是經過一些簡單的例子介紹了Dagger2的相關概念及使用,實際項目中的應用遠比這裏的例子要複雜。關於Dagger2在實際項目中的應用能夠參照這個開源項目 github.com/BaronZ88/Mi…(項目採用MVP架構,其中View層和Presenter層的解耦就是經過Dagger2來實現的)。

MinimalistWeather是一款開源天氣App,開發此項目主要是爲展現各類開源庫的使用方式以及Android項目的架構方案,並做爲團隊開發規範的一部分。項目中每個字母、每個命名、每一行代碼都是通過仔細考究的;可是因爲時間精力有限,項目UI未作嚴格要求。本着精益求精、提供更好開源項目和更美天氣應用的原則,所以指望有興趣的開發和UED同窗能夠一塊兒來完成這個項目。

本次掘金徵文活動連接gold.xitu.io/post/58522d…

相關文章
相關標籤/搜索