Dagger 2 完全解析(一),Dagger 2 的基本使用與原理

Dagger 2 完全解析系列:

Dagger 2 完全解析(一),Dagger 2 的基本使用與原理

Dagger 2 完全解析(二),進(jìn)階使用 Lazy、Qualifier、Scope 等

Dagger 2 完全解析(三),Component 的組織關(guān)系與 SubComponent

Dagger 2 完全解析(四),Android 中使用 Dagger 2

Dagger 2 完全解析(五),Kotlin 中使用 Dagger 2

Dagger 2 完全解析(六),dagger.android 擴展庫的使用

上面文章地址是我博客上的地址,本系列文章是基于 Google Dagger 2.11-rc2 版本

依賴注入

什么是依賴

如果在 Class A 中,有 Class B 的實例,則稱 Class A 對 Class B 有一個依賴。例如 Man 中有用到一個 Car 對象,即 Man 對 Car 有一個依賴。

public class Man {
    Car car;
    public Man() {
        car = new Car();
    }
    ...
}

上面這種寫法是最常見的寫法,但是在下面幾個場景中存在一些問題:

  1. 如果要修改 Car 的構(gòu)造函數(shù),例如需要使用car = new Car(name)的方式構(gòu)造時,還要修改 Man 的代碼;

  2. 如果想測試不同的 Car 對 Man 的影響會很困難,例如單元測試中使用 mock 的 car 測試 Man。

什么是依賴注入

依賴注入(Dependency Injection,簡稱 DI)是用于實現(xiàn)控制反轉(zhuǎn)(Inversion of Control,縮寫為 IoC)最常見的方式之一,控制反轉(zhuǎn)是面向?qū)ο缶幊讨械囊环N設(shè)計原則,用以降低計算機代碼之間耦合度。控制反轉(zhuǎn)的基本思想是:借助“第三方”實現(xiàn)具有依賴關(guān)系的對象之間的解耦。一開始是對象 A 對 對象 B 有個依賴,對象 A 主動地創(chuàng)建 對象 B,對象 A 有主動控制權(quán),實現(xiàn)了 Ioc 后,對象 A 依賴于 Ioc 容器,對象 A 被動地接受容器提供的對象 B 實例,由主動變?yōu)楸粍樱虼朔Q為控制反轉(zhuǎn)。注意,控制反轉(zhuǎn)不等同于依賴注入,控制反轉(zhuǎn)還有一種實現(xiàn)方式叫“依賴查找”(Denpendency Lookup)。更多控制反轉(zhuǎn)的信息請看控制反轉(zhuǎn)的維基百科

依賴注入就是將對象實例傳入到一個對象中去(Denpendency injection means giving an object its instance variables)。依賴注入是一種設(shè)計模式,降低了依賴和被依賴對象之間的耦合,方便擴展和單元測試。

依賴注入的實現(xiàn)方式

其實在平常編碼的過程中,已經(jīng)不知覺地使用了依賴注入

  • 基于構(gòu)造函數(shù),在構(gòu)造對象時注入所依賴的對象。
public class Man {
    Car car;
    public Man(Car car) {
        this.car = car;
    }
    ...
}
  • 基于 set 方法,使用 setter 方法來讓外部容器調(diào)用傳入所依賴的對象。
public class Man {
    ...
    public void setCar(Car car) {
        this.car = car;
    }
}
  • 基于接口,使用接口來提供 setter 方法。
public interface CarInjector {
    void injectCar(Car car);
}

public class Man implements CarInjector {
    ...
    @Override
    public void injectCar(Car car) {
        this.car = car;
    }
}
  • 基于注解,Dagger 2 依賴注入框架就是使用@Inject完成注入。
public class Man {
    @Inject
    Car car;
    ...
}

Dagger 2

Dagger 2 是 Java 和 Android 下的一個完全靜態(tài)、編譯時生成代碼的依賴注入框架,由 Google 維護(hù),早期的版本 Dagger 是由 Square 創(chuàng)建的。

Dagger 2 是基于 Java Specification Request(JSR) 330標(biāo)準(zhǔn)。利用 JSR 注解在編譯時生成代碼,來注入實例完成依賴注入。

下面是 Dagger 2 的一些資源地址:

Github:https://github.com/google/dagger

官方文檔:https://google.github.io/dagger//

API:http://google.github.io/dagger/api/latest/

Dagger 2 的基本使用

上面介紹了依賴注入和 Dagger 2,下面由簡單的示例開始一步一步地解析 Dagger 2 的基本使用與原理。

引入 Dagger 2

build.gradle中添加依賴:

dependencies {
    ...
    compile 'com.google.dagger:dagger:2.11-rc2'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.11-rc2'
}

如果 Android gradle plugin 的版本低于2.2,還需要引入 android-apt 插件。

使用 @Inject 標(biāo)注需要注入的依賴

繼續(xù)使用上面 Man 的例子:

public class Man {
    @Inject
    Car car;
    ...
}

使用javax.inject.Inject注解來標(biāo)注需要 Dagger 2 注入的依賴,build 后可以在build/generated/source/apt目錄下看到 Dagger 2 編譯時生成的成員屬性注入類。

public final class Man_MembersInjector implements MembersInjector<Man> {
  private final Provider<Car> carProvider;

  public Man_MembersInjector(Provider<Car> carProvider) {
    assert carProvider != null;
    this.carProvider = carProvider;
  }

  public static MembersInjector<Man> create(Provider<Car> carProvider) {
    return new Man_MembersInjector(carProvider);
  }

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

  public static void injectCar(Man instance, Provider<Car> carProvider) {
    instance.car = carProvider.get();
  }
}

從上面的injectMembers方法中可以看到注入依賴的代碼是instance.car = carProvider.get();,所以@Inject標(biāo)注的成員屬性不能是private的,不然無法注入。

創(chuàng)建所依賴對象的實例

@Inject標(biāo)注構(gòu)造函數(shù)時,Dagger 2 會完成實例的創(chuàng)建。

public class Car {
    @Inject
    public Car() {}
}

build 后可以在build/generated/source/apt目錄下看到 Dagger 2 編譯時生成的工廠類。

public final class Car_Factory implements Factory<Car> {
  private static final Car_Factory INSTANCE = new Car_Factory();

  @Override
  public Car get() {
    return new Car();
  }

  public static Factory<Car> create() {
    return INSTANCE;
  }
}

依賴注入是依賴的對象實例-->需要注入的實例屬性,上面完成兩步,通過 Dagger 2 生成的代碼代碼可以知道,生成了 Man 的成員屬性注入類和 Car 的工廠類,接下來需要的就是新建工廠實例并調(diào)用成員屬性注入類完成 car 的實例注入。完成這個過程的橋梁就是dagger.Component

Component 橋梁

@Component可以標(biāo)注接口或抽象類,Component 橋梁可以完成依賴注入過程,其中最重要的是定義注入接口,調(diào)用注入接口就可以完成 Man 所需依賴的注入。

@Component
public interface ManComponent {

    void injectMan(Man man);  // 注入 man 所需要的依賴

}

build 后會生成帶有Dagger前綴的實現(xiàn)該接口的類:DaggerManComponent

public final class DaggerManComponent implements ManComponent {
  private MembersInjector<Man> ManMembersInjector;

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

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

  public static ManComponent create() {
    return new Builder().build();
  }

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

    this.ManMembersInjector = Man_MembersInjector.create(Car_Factory.create());    // 找到 Man 的成員屬性注入類,創(chuàng)建依賴的工廠
  }

  @Override
  public void injectMan(Man man) {
    ManMembersInjector.injectMembers(man);  // 完成依賴注入
  }

  public static final class Builder {
    private Builder() {}

    public ManComponent build() {
      return new DaggerManComponent(this);
    }
  }
}

從上面生成的代碼可以看出來 Component 就是連接依賴的對象實例需要注入的實例屬性之間的橋梁。Component 會查找目標(biāo)類對應(yīng)的成員屬性注入類(即 MembersInjector<Man>),然后把依賴屬性的工廠實例(即 Car_Factory.create())傳給注入類,再使用 Component 一開始定義的接口就能完成依賴注入。注意,Component 中注入接口的參數(shù)必須為需要注入依賴的類型,不能是 Man 的父類或子類,注入接口返回值為 void,接口名可以任意。

接下來只需要在 Man 中調(diào)用injectMan方法就能完成注入。

public class Man {
    ...
    public Man() {
        DaggerManComponent.create().injectMan(this);
    }
    ...
}

Module

使用@Inject標(biāo)注構(gòu)造函數(shù)來提供依賴的對象實例的方法,不是萬能的,在以下幾種場景中無法使用:

  • 接口沒有構(gòu)造函數(shù)

  • 第三方庫的類不能被標(biāo)注

  • 構(gòu)造函數(shù)中的參數(shù)必須配置

這時,就可以用@Provides標(biāo)注的方法來提供依賴實例,方法的返回值就是依賴的對象實例,@Provides方法必須在Module中,Module 即用@Module標(biāo)注的類。所以 Module 是提供依賴的對象實例的另一種方式。

@Module
public class CarModule {
    @Provides
    static Car provideCar() {
        return new Car();
    }
}

約定俗成的是@Provides方法一般以provide為前綴,Moudle 類以Module為后綴,一個 Module 類中可以有多個@Provides方法。

接下來,需要把可以提供依賴實例的 Module 告訴 Component:

@Component(modules = CarModule.class)
public interface ManComponent {

    void injectMan(Man man);  // 注入 man 所需要的依賴

}

build之后,Module 和 Component 生成的類為:

public final class CarModule_ProvideCarFactory implements Factory<Car> {
  private static final CarModule_ProvideCarFactory INSTANCE = new CarModule_ProvideCarFactory();

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

  public static Factory<Car> create() {
    return INSTANCE;
  }

  /** Proxies {@link CarModule#provideCar()}. */
  public static Car proxyProvideCar() {
    return CarModule.provideCar();
  }
}

CarModule_ProvideCarFactory 和之前的 Car_Factory 類似,都實現(xiàn) Factory<Car> 接口。

生成的 DaggerManComponent 和之前相比只改變了一個方法:

private void initialize(final Builder builder) {
    this.manMembersInjector = Man_MembersInjector.create(CarModule_ProvideCarFactory.create());
}

只是提供依賴實例的工廠變?yōu)榱?CarModule 對應(yīng)的工廠。

總結(jié)

現(xiàn)在再來看 Dagger 2 最核心的三個部分:

  1. 需要注入依賴的目標(biāo)類,需要注入的實例屬性由@Inject標(biāo)注。

  2. 提供依賴對象實例的工廠,用@Inject標(biāo)注構(gòu)造函數(shù)或定義Module這兩種方式都能提供依賴實例,Dagger 2 的注解處理器會在編譯時生成相應(yīng)的工廠類。Module的優(yōu)先級比@Inject標(biāo)注構(gòu)造函數(shù)的高,意味著 Dagger 2 會先從 Module 尋找依賴實例。

  3. 把依賴實例工廠創(chuàng)建的實例注入到目標(biāo)類中的 Component。

下面再講述上面提到的在 Dagger 2 種幾個注解的用法:

  • @Inject 一般情況下是標(biāo)注成員屬性和構(gòu)造函數(shù),標(biāo)注的成員屬性不能是private,Dagger 2 還支持方法注入,@Inject還可以標(biāo)注方法。

  • @Provides 只能標(biāo)注方法,必須在 Module 中。

  • @Module 用來標(biāo)注 Module 類

  • @Component 只能標(biāo)注接口或抽象類,聲明的注入接口的參數(shù)類型必須和目標(biāo)類一致。

推薦閱讀:

想看更多精彩內(nèi)容,歡迎關(guān)注我的公眾號 JohnnyShieh,每周一準(zhǔn)時更新!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內(nèi)容