神兵利器Dragger2學習篇

Dagger ,早有耳聞,但一直沒有與他正真的認識過。Dagger 英文翻譯爲“匕首”,這可能就是把它稱作神兵利器的原因。當我看一些開源項目在使用dagger2 時,我停止了前進的步伐,查閱了網上一些層差不齊的資料後,感覺還是懵逼狀態,正當我感受到了從入門到放棄的狀態時,看到了一篇相對好理解一寫的博文,我又開始踏上了從放棄到入門之路。

背景(日常唸經,瞭解一下)

Dagger ,鼎鼎大名的Square公司 開發的,光聽Square,就知道肯定不簡單(okhttp,Picasso,leakcanary,等等等等)起初Square公司受到Guice的啓發而開發了Dagger,但是Dagger這種半靜態半運行時的框架還是有些性能問題(雖說依賴注入是完全靜態的,但是其有向無環圖(Directed Acyclic Graph)還是基於反射來生成的,這無論在大型的服務端應用還是在Android應用上都不是最優方案)。因此Google工程師Fork了Dagger項目,對它進行了改造。於是變演變出了今天我們要討論的Dagger2,所以說Dagger2其實就是高配版的Dagger。

Dagger2 基於Java註解來實現的完全在編譯階段完成依賴注入的開源庫,主要用於模塊間解耦、提高代碼的健壯性和可維護性。Dagger2在編譯階段通過apt利用Java註解自動生成Java代碼,然後結合手寫的代碼來自動幫我們完成依賴注入的工作。

依賴注入

上文中提到了Dagger2 是解決Android或java中依賴注入的一個類庫(Dependency Injection類庫) ,通過註解在編譯期生成代碼的方式實現注入。那爲什麼要用依賴注入呢?首先看個例子

上面兩張圖很形象的說明了使用依賴的差別,只不過用的是構造函數傳參給成員變量賦值,實現注入(後面簡稱構造注入)。前者在編碼中肯定不提倡,因爲Major和 Student耦合性太高。當然,除了構造函數注入還有接口注入:實現接口方法,同樣以傳參的方式實現注入。

public interface Injection<T>{
    void inject(T t);
}
public class Student implements Injection<Engine>{
    private Major major;
    public Student(){}
    public void inject(Major major){
        this.major = major;
    }
}

重點來了當然,前兩者注入方式需要編寫大量代碼,Dagger2是通過Java註解在編譯期來實現依賴注入的

/**
 * 創建時間:2018/8/23
 * 編寫人:kanghb
 * 功能描述:學生
 */
public class Student {

    @Inject
    Major major;

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "major=" + major +
                '}';
    }
}

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標註的接口爲StudentComponent,則編譯期生成的實現類爲DaggerStudentComponent),我們通過調用這個實現類的方法完成注入;

  • @Qulifier:@Qulifier用於自定義註解,也就是說@Qulifier就如同Java提供的幾種基本元註解一樣用來標記註解類。我們在使用@Module來標註提供依賴的方法時,方法名我們是可以隨便定義的(雖然我們定義方法名一般以provide開頭,但這並不是強制的,只是爲了增加可讀性而已)。那麼Dagger2怎麼知道這個方法是爲誰提供依賴呢?答案就是返回值的類型,Dagger2根據返回值的類型來決定爲哪個被@Inject標記了的變量賦值。但是問題來了,一旦有多個一樣的返回類型Dagger2就懵逼了。@Qulifier的存在正式爲了解決這個問題,我們使用@Qulifier來定義自己的註解,然後通過自定義的註解去標註提供依賴的方法和依賴需求方(也就是被@Inject標註的變量),這樣Dagger2就知道爲誰提供依賴了。—-一個更爲精簡的定義:當類型不足以鑑別一個依賴的時候,我們就可以使用這個註解標示;

  • @Scope:@Scope同樣用於自定義註解,我能可以通過@Scope自定義的註解來限定註解作用域,實現局部的單例;

  • @Singleton:@Singleton其實就是一個通過@Scope定義的註解,我們一般通過它來實現全局單例。但實際上它並不能體現全局單例,是否能提供全局單例還要取決於對應的Component是否爲一個全局對象。

Dagger2使用

一、@inject標註構造方法提供依賴

1.添加依賴

// Add Dagger dependencies
dependencies {
  compile 'com.google.dagger:dagger:2.x'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
}

關於Dagger2的依賴配置就不在這裏佔用篇幅去描述了,大家可以到它的github主頁下去查看官方教程https://github.com/google/dagger。接下來我們拿Car和Engine來舉例。

2.Student類是依賴需求方,依賴了Major類;因此我們需要在類變量Major上添加@Inject來告訴Dagger2來爲自己提供依賴。

/**
 * 創建時間:2018/8/23
 * 編寫人:kanghb
 * 功能描述:學生(依賴需求方)
 */
public class Student {

    @Inject
    Major major;

    public Student() {

    }

    @Override
    public String toString() {
        return "Student{" +
                "major=" + major +
                '}';
    }
}

3.Major類是依賴提供方,因此我們需要在它的構造函數上添加@Inject

/**
 * 創建時間:2018/8/23
 * 編寫人:kanghb
 * 功能描述:專業(依賴提供方)
 */
public class Major {
    @Inject
    public Major() {
    }
    public void test(){
        System.out.println("我是Major的方法");
    }
}

4.創建一個用@Component標註的接口StudentComponent,這個StudentComponent其實就是一個注入器,這裏用來將Major注入到Student中。

/**
 * 創建時間:2018/8/24
 * 編寫人:kanghb
 * 功能描述:
 */
@Component
public interface StudentComponent {
    void bind(Student student);
}

注意:看一些文檔裏這裏bind方法命名都是用inject的,我不知道是不是強制必須inject,所以寫了bind看看測試一下,稍後揭曉結果。

5.上述操作完成後Build下項目,讓Dagger2幫我們生成相關的Java類。

 

6.Student的構造函數中調用Dagger2生成的DaggerStudentComponent來實現注入。

Student類完整代碼如下

/**
 * 創建時間:2018/8/23
 * 編寫人:kanghb
 * 功能描述:學生(需求依賴方)
 */
public class Student {

    @Inject
    Major major;

    public Student() {
        DaggerStudentComponent.builder().build().bind(this);
    }

    @Override
    public String toString() {
        return "Student{" +
                "major=" + major +
                '}';
    }

    public Major getMajor() {
        return major;
    }

}

7.查看運行結果如下,證明我們前面說的inject方法不是強制的但有益於提升代碼的可讀性)

 Student student = new Student();
 student.getMajor().test();
08-24 01:57:58.035 2459-2459/kanghb.com.dagger2demo I/System.out: 我是Major的方法

二、@Module +@Provide標註構造方法提供依賴

如果創建Major的構造函數是帶參數的呢?比如說製造一各專業是需要教師(teacher)的。或者Major類是我們無法修改的呢(依賴第三方,無法修改構造函數)?這時候就需要@Module和@Provide上場了。

1.在Student類的成員變量Major上加上@Inject表示自己需要Dagger2爲自己提供依賴;Major類的構造函數上的@Inject也需要去掉,因爲現在不需要通過構造函數上的@Inject來提供依賴了。

public class Student {

    @Inject
    Major major;

    public Student() {
        DaggerStudentComponent.builder().makeMajorModule(new                                 MakeMajorModule()).build().inject(this);
    }

    @Override
    public String toString() {
        return "Student{" +
                "major=" + major +
                '}';
    }

    public Major getMajor() {
        return major;
    }

}

2.新建個Module類來生成依賴對象。前面介紹的@Module就是用來標準這個類的,而@Provide則是用來標註具體提供依賴對象的方法(這裏有個不成文的規定,被@Provide標註的方法命名我們一般以provide開頭,這並不是強制的但有益於提升代碼的可讀性)

@Module
public class MakeMajorModule {
    public MakeMajorModule() {
    }

    @Provides
    public Major provideMajor(){
        return new Major("kanghanbin");
    }
}

3.對StudentComponent進行一點點修改,之前的@Component註解是不帶參數的,現在我們需要加上modules = {MakeMajorModule.class},用來告訴Dagger2提供依賴的是MakeMajorModule這個類。

@Component(modules = {MakeMajorModule.class})
public interface StudentComponent {
    void inject(Student student);
}

4.Student類的構造函數我們也需要修改,相比之前多了個markCarModule(new MarkCarModule())方法,這就相當於告訴了注入器DaggerStudentComponentMakeMajorModule提供的依賴注入到了Student類中



public Student() {
    DaggerStudentComponent.builder().makeMajorModule(new MakeMajorModule()).build().inject(this);
}

注意:其實這裏加不加 makeMajorModule(new MakeMajorModule())方法,都能順利執行,爲什麼呢,打開DaggerStudentComponent一探究竟,發現就算不使用 makeMajorModule(new MakeMajorModule())方法,在調用build方法時,也直接 new MakeMajorModule()。

public static final class Builder {
  private MakeMajorModule makeMajorModule;

  private Builder() {}

  public StudentComponent build() {
    if (makeMajorModule == null) {
      this.makeMajorModule = new MakeMajorModule();
    }
    return new DaggerStudentComponent(this);
  }

  public Builder makeMajorModule(MakeMajorModule makeMajorModule) {
    this.makeMajorModule = Preconditions.checkNotNull(makeMajorModule);
    return this;
  }
}

5.執行結果同樣是

08-24 01:57:58.035 2459-2459/kanghb.com.dagger2demo I/System.out: 我是Major的方法

三、@Qualifier實現一個類中有兩個相同類型不同對象

Dagger2根據返回值的類型來決定爲哪個被@Inject標記了的變量賦值。但是問題來了,一旦有多個一樣的返回類型Dagger2就懵逼了。@Qulifier的存在正式爲了解決這個問題,我們使用@Qulifier來定義自己的註解,然後通過自定義的註解去標註提供依賴的方法和依賴需求方。

1.使用Qulifier定義兩個註解:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierA {

}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifierB {

}

2.對依賴提供方做出修改

@Module
public class MakeMajorModule {
    public MakeMajorModule() {
    }
    @QualifierA
    @Provides
    public Major provideMajorA(){
        return new Major("kanghanbin");
    }
    @QualifierB
    @Provides
    public Major provideMajorB(){
        return new Major("fenglin");
    }
}

3.對依賴需求方做出修改

public class Student {

    @QualifierA
    @Inject
    Major major;
    @QualifierB
    @Inject
    Major major2;

    public Student() {
        DaggerStudentComponent.builder().makeMajorModule(new MakeMajorModule()).build().inject(this);
    }

    @Override
    public String toString() {
        return "Student{" +
                "major=" + major +
                '}';
    }

    public Major getMajorA() {
        return major;
    }
    public Major getMajorB() {
        return major2;
    }

4.添加測試代碼查看運行結果

  Student student = new Student();
  student.getMajorA().test();
  student.getMajorB().test();
08-24 03:32:20.678 7632-7632/kanghb.com.dagger2demo I/System.out: 我是teacher爲kanghanbinMajor的方法
08-24 03:32:20.679 7632-7632/kanghb.com.dagger2demo I/System.out: 我是teacher爲fenglinMajor的方法

四、@Scope限定作用域

1.首先我們需要通過@Scope定義一個StudentScope註解:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface StudentScope {
}

2.接着我們需要用這個@StudentScope去標記依賴提供方MakeMajorModule

@StudentScope
@Provides
public Major provideMajorA(){
    return new Major("kanghanbin");
}

3.用@StudentScope去標註注入器 StudentComponent

@StudentScope
@Component(modules = {MakeMajorModule.class})
public interface StudentComponent {
    void inject(Student student);
}

4.Student類改爲

@Inject
Major major;
@Inject
Major major2;

5.Major類改爲

public Major(String teacher) {
    this.teacher = teacher;
    System.out.println("Create a Major");
}

public void test(){
    System.out.println("我是teacher爲"+ teacher + "Major的方法");
}

輸出結果:

Create a Major

但是如果我麼不加@StudentScope,就打印出兩次

Create a Major
Create a Major

所以,通過@Scope實現了局部的單例。

dagger2原理分析

1.MakeMajorModuleMakeMajorModule_ProvideMajorAFactory


自己寫的類
@Module
public class MakeMajorModule {
    public MakeMajorModule() {
    }
//    @QualifierA
    @StudentScope
    @Provides
    public Major provideMajorA(){
        return new Major("kanghanbin");
    }  
}


 Dagger2生成的工廠類
public final class MakeMajorModule_ProvideMajorAFactory implements Factory<Major> {
  private final MakeMajorModule module;

  public MakeMajorModule_ProvideMajorAFactory(MakeMajorModule module) {
    assert module != null;
    this.module = module;
  }

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

  public static Factory<Major> create(MakeMajorModule module) {
    return new MakeMajorModule_ProvideMajorAFactory(module);
  }

可以看到dagger2依據我們寫的類給我們提供了一個工廠類,get()方法調用了MakeMajorModuleprovideMajorA()拿到了Major,create(MakeMajorModule module)通過傳進來的MakeMajorModule 創建工廠類實例。

2.StudentComponent 和DaggerStudentComponent


自己寫的類
@StudentScope
@Component(modules = {MakeMajorModule.class})
public interface StudentComponent {
    void inject(Student student);
}


 Dagger2生成的StudentComponent的實現類
 public final class DaggerStudentComponent implements StudentComponent {
  private Provider<Major> provideMajorAProvider;

  private MembersInjector<Student> studentMembersInjector;

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

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

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

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

    this.provideMajorAProvider =
        DoubleCheck.provider(MakeMajorModule_ProvideMajorAFactory.create(builder.makeMajorModule));

    this.studentMembersInjector = Student_MembersInjector.create(provideMajorAProvider);
  }

  @Override
  public void inject(Student student) {
    studentMembersInjector.injectMembers(student);
  }

  public static final class Builder {
    private MakeMajorModule makeMajorModule;

    private Builder() {}

    public StudentComponent build() {
      if (makeMajorModule == null) {
        this.makeMajorModule = new MakeMajorModule();
      }
      return new DaggerStudentComponent(this);
    }

    public Builder makeMajorModule(MakeMajorModule makeMajorModule) {
      this.makeMajorModule = Preconditions.checkNotNull(makeMajorModule);
      return this;
    }
  }
}

DaggerStudentComponent就是StudentComponent 的實現類,通過builder()方法返回了Builder對象,然後build創建了一個DaggerStudentComponent對象。在構造函數中初始化了provideMajorAProvider 和 studentMembersInjector。當調用inject時候,執行 studentMembersInjector.injectMembers(student)

  1. 分析上一步提到的Student_MembersInjector

public final class Student_MembersInjector implements MembersInjector<Student> {
  private final Provider<Major> majorAndMajor2Provider;

  public Student_MembersInjector(Provider<Major> majorAndMajor2Provider) {
    assert majorAndMajor2Provider != null;
    this.majorAndMajor2Provider = majorAndMajor2Provider;
  }

  public static MembersInjector<Student> create(Provider<Major> majorAndMajor2Provider) {
    return new Student_MembersInjector(majorAndMajor2Provider);
  }

  @Override
  public void injectMembers(Student instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
    instance.major = majorAndMajor2Provider.get();
    instance.major2 = majorAndMajor2Provider.get();
  }

  public static void injectMajor(Student instance, Provider<Major> majorProvider) {
    instance.major = majorProvider.get();
  }

  public static void injectMajor2(Student instance, Provider<Major> major2Provider) {
    instance.major2 = major2Provider.get();
  }
}

create方法在DaggerStudentComponent中被調用用來創建一個Student_MembersInjector對象,injectMembers()方法也是在DaggerStudentComponent的inject中被調用初始化,用majorAndMajor2Provider.get()來初始化Student中的兩個Major對象,從而Student依賴需求方就得到了major和major2的實例。而這裏的majorAndMajor2Provider.get()就是MakeMajorModule_ProvideMajorAFactory裏面的get方法。

換一種角度去思考(結合方法調用順序再來一波分析)

首先Major構造方法是在provideMajor被調用的,然後看看是誰調用了provideMajor這個方法,發現是被MakeMajorModule_ProvideMajorFactory類裏的get方法調用,再看看是誰調用了get方法,看到了是由Student_MembersInjectorinjectMembers方法調用,而injectMembers正是在DaggerStudentComponent的inject方法中執行的,瞬間恍然大悟,從後往前看更容易理解

@Provides
public Major provideMajor(){
    return new Major("kanghanbin");
}
  @Override
  public Major get() {
    return Preconditions.checkNotNull(
        module.provideMajor(), "Cannot return null from a non-@Nullable @Provides method");
  }
  @Override
  public void injectMembers(Student instance) {
    if (instance == null) {
      throw new NullPointerException("Cannot inject members into a null reference");
    }
      //都調用的是
    instance.major = majorAndMajor2Provider.get();
    instance.major2 = majorAndMajor2Provider.get();
  }
  @Override
  public void inject(Student student) {
    studentMembersInjector.injectMembers(student);
  }

總結

本文只是簡單的對dagger2分析了一下,沒有真正在安卓項目中去運用,在開發安卓App過程中會遇到的比這更復雜,但是相信掌握了本篇所講內容,再去結合實際開發去用它就沒有那麼難了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章