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

Dagger 2 完全解析系列:

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

Dagger 2 完全解析(二)输硝,進階使用 Lazy今瀑、Qualifier、Scope 等

Dagger 2 完全解析(三)点把,Component 的組織關系與 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 的構造函數各聘,例如需要使用car = new Car(name)的方式構造時,還要修改 Man 的代碼抡医;

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

什么是依賴注入

依賴注入(Dependency Injection忌傻,簡稱 DI)是用于實現控制反轉(Inversion of Control大脉,縮寫為 IoC)最常見的方式之一,控制反轉是面向對象編程中的一種設計原則水孩,用以降低計算機代碼之間耦合度镰矿。控制反轉的基本思想是:借助“第三方”實現具有依賴關系的對象之間的解耦俘种。一開始是對象 A 對 對象 B 有個依賴衡怀,對象 A 主動地創(chuàng)建 對象 B,對象 A 有主動控制權安疗,實現了 Ioc 后抛杨,對象 A 依賴于 Ioc 容器,對象 A 被動地接受容器提供的對象 B 實例荐类,由主動變?yōu)楸粍硬老郑虼朔Q為控制反轉。注意玉罐,控制反轉不等同于依賴注入屈嗤,控制反轉還有一種實現方式叫“依賴查找”(Denpendency Lookup)。更多控制反轉的信息請看控制反轉的維基百科吊输。

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

依賴注入的實現方式

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

  • 基于構造函數扭屁,在構造對象時注入所依賴的對象算谈。
public class Man {
    Car car;
    public Man(Car car) {
        this.car = car;
    }
    ...
}
  • 基于 set 方法,使用 setter 方法來讓外部容器調用傳入所依賴的對象料滥。
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 維護鲸匿,早期的版本 Dagger 是由 Square 創(chuàng)建的雁社。

Dagger 2 是基于 Java Specification Request(JSR) 330標準。利用 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 標注需要注入的依賴

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

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

使用javax.inject.Inject注解來標注需要 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標注的成員屬性不能是private的剥啤,不然無法注入锦溪。

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

@Inject標注構造函數時,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 的工廠類,接下來需要的就是新建工廠實例并調用成員屬性注入類完成 car 的實例注入冲簿。完成這個過程的橋梁就是dagger.Component粟判。

Component 橋梁

@Component可以標注接口或抽象類,Component 橋梁可以完成依賴注入過程峦剔,其中最重要的是定義注入接口档礁,調用注入接口就可以完成 Man 所需依賴的注入。

@Component
public interface ManComponent {

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

}

build 后會生成帶有Dagger前綴的實現該接口的類: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 會查找目標類對應的成員屬性注入類(即 MembersInjector<Man>),然后把依賴屬性的工廠實例(即 Car_Factory.create())傳給注入類野舶,再使用 Component 一開始定義的接口就能完成依賴注入易迹。注意,Component 中注入接口的參數必須為需要注入依賴的類型平道,不能是 Man 的父類或子類,注入接口返回值為 void供炼,接口名可以任意一屋。

接下來只需要在 Man 中調用injectMan方法就能完成注入窘疮。

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

Module

使用@Inject標注構造函數來提供依賴的對象實例的方法,不是萬能的冀墨,在以下幾種場景中無法使用:

  • 接口沒有構造函數

  • 第三方庫的類不能被標注

  • 構造函數中的參數必須配置

這時闸衫,就可以用@Provides標注的方法來提供依賴實例,方法的返回值就是依賴的對象實例诽嘉,@Provides方法必須在Module中蔚出,Module 即用@Module標注的類。所以 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 類似盒蟆,都實現 Factory<Car> 接口踏烙。

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

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

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

總結

現在再來看 Dagger 2 最核心的三個部分:

  1. 需要注入依賴的目標類历等,需要注入的實例屬性由@Inject標注讨惩。

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

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

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

  • @Inject 一般情況下是標注成員屬性和構造函數,標注的成員屬性不能是private要出,Dagger 2 還支持方法注入鸳君,@Inject還可以標注方法。

  • @Provides 只能標注方法患蹂,必須在 Module 中或颊。

  • @Module 用來標注 Module 類

  • @Component 只能標注接口或抽象類,聲明的注入接口的參數類型必須和目標類一致传于。

推薦閱讀:

想看更多精彩內容囱挑,歡迎關注我的公眾號 JohnnyShieh,每周一準時更新沼溜!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末平挑,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌通熄,老刑警劉巖唆涝,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異唇辨,居然都是意外死亡廊酣,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門赏枚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來亡驰,“玉大人,你說我怎么就攤上這事饿幅》踩瑁” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵诫睬,是天一觀的道長煞茫。 經常有香客問我,道長摄凡,這世上最難降的妖魔是什么续徽? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮亲澡,結果婚禮上钦扭,老公的妹妹穿的比我還像新娘。我一直安慰自己床绪,他們只是感情好客情,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著癞己,像睡著了一般膀斋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上痹雅,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天仰担,我揣著相機與錄音,去河邊找鬼绩社。 笑死摔蓝,一個胖子當著我的面吹牛,可吹牛的內容都是我干的愉耙。 我是一名探鬼主播贮尉,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼朴沿!你這毒婦竟也來了猜谚?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎龄毡,沒想到半個月后吠卷,有當地人在樹林里發(fā)現了一具尸體锡垄,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡沦零,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了货岭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片路操。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖千贯,靈堂內的尸體忽然破棺而出屯仗,到底是詐尸還是另有隱情,我是刑警寧澤搔谴,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布魁袜,位于F島的核電站,受9級特大地震影響敦第,放射性物質發(fā)生泄漏峰弹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一芜果、第九天 我趴在偏房一處隱蔽的房頂上張望鞠呈。 院中可真熱鬧,春花似錦右钾、人聲如沸蚁吝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窘茁。三九已至,卻和暖如春脆烟,著一層夾襖步出監(jiān)牢的瞬間山林,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工浩淘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捌朴,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓张抄,卻偏偏與公主長得像砂蔽,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子署惯,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容