Dagger2學習筆記

Dagger2是第一個使用生成代碼的方式實現依賴注入的框架。做爲Dagger的升級版本,天然有它的優點,優先注重的是執行效率。本文着重介紹Dagger2。官方據點傳送門: https://google.github.io/dagger//users-guide.htmlhtml

首先來看一下依賴注入和控制反轉java

在軟件工程領域,DI是一種實現控制反轉用來解決依賴的設計模式,依賴是一個能夠被使用的對象(服務),注入是把依賴傳遞給依賴它的對象(客戶),即要使用它的對象,這樣,服務就成了客戶組成的一部分。傳遞服務到客戶,而不是讓客戶建立或尋找服務,是這個模式的基本要求。git

依賴注入容許程序設計遵循依賴倒置原則,客戶把爲它提供依賴的責任委託給外部代碼(依賴注入器),它自身不容許使用注入器的代碼,而是注入器建立服務並把他們注入到客戶。這意味着客戶不須要知道注入器代碼,不須要知道如何建立服務,不須要知道本身使用的具體是什麼服務,而只須要知道如何使用服務的接口定義。這就分離了建立和使用的關係。github

客戶接受依賴注入有三種方式:算法

  1. 1.      基於Setter
  2. 2.      基於接口
  3. 3.      基於構造函數

Setter和構造函數注入的方式主要看何時會使用到依賴,接口注入方式在於依賴能夠控制本身的注入。三者都須要獨立的注入代碼來負責引入客戶和依賴之間的相互關係。後端

控制反轉即IoC (Inversion of Control),它把傳統上由程序代碼直接操控的對象的調用權交給容器,經過容器來實現對象組件的裝配和管理。所謂的「控制反轉」概念就是對組件對象控制權的轉移,從程序代碼自己轉移到了外部容器。引用51CTO的一張圖:設計模式

 

實現控制反轉主要有兩種方式:依賴注入和依賴查找。二者的區別在於,前者是被動的接收對象,在類A的實例建立過程當中即建立了依賴的B對象,經過類型或名稱來判斷將不一樣的對象注入到不一樣的屬性中,然後者是主動索取響應名稱的對象,得到依賴對象的時間也能夠在代碼中自由控制。api

 

Dagger2數組

Dagger2是第一個使用生成代碼的方式實現依賴注入的框架。指導思想是模擬生成開發者寫的代碼來確保依賴注入足夠簡單,可跟蹤、可執行。緩存

Dagger2建立類的實例並知足他們的依賴,它使用javax.inject.Inject 註解來識別感興趣的構造函數和字段。

如下大部份內容譯自官網:https://google.github.io/dagger//users-guide.html

這裏使用一個CoffeeApp的例子來講明如何使用Dagger。

聲明依賴

使用@Inject來註解Dagger2應該用來建立對象實例的構造函數,當一個新的對象實例被請求的時候,Dagger會取得要求的參數(若是有)並調用這個構造函數。

class Thermosiphon implements Pump {

  private final Heater heater;

 

  @Inject

  Thermosiphon(Heater heater) {

    this.heater = heater;

  }

 

  ...

}

 

Dagger可以直接注入字段。在下面的例子中,它爲heater字段取得Heater的實例,爲pump字段取得Pump的實例。

class CoffeeMaker {
  @Inject Heater heater;
  @Inject Pump pump;
 
  ...
}

 

若是你的類有@Inject註解的字段卻沒有對應的@Inject註解的構造函數,在請求這些字段時Dagger也會注入他們,可是不會去建立新的對象。你能夠添加並註解一個無參構造函數來指示Dagger能夠建立對象實例。

Dagger也支持方法注入,儘管構造函數和字段注入更受歡迎。

方法注入就是用@Inject註解T的Public方法,Dagger會在(自動或手動)調用T_MembersInjector. injectMembers(T)方法時提供方法所須要的參數依賴並調用該方法(即跟注入字段在同一時機)。

Dagger沒法建立沒有使用@Inject註解構造函數的類的實例。

 

知足依賴

默認狀況下,Dagger會像上面描述的建立所請求的類的實例來知足每個依賴。當你請求一個CoffeeMaker的時候,它會經過調用new CoffeeMaker()並注入可注入的字段來獲取一個實例。可是@Inject在下面情形時無能爲力:

  1. Interface沒法實例化。
  2. 第三方類沒法註解。
  3. 可配置的對象必須採用配置的方式。

在這些@Inject無能爲力的地方,可使用@Provides註解的方法來知足依賴。方法的返回類型定義了要知足的依賴。

例如,當Heater被請求時,provideHeater()方法會被調用。

@Provides static Heater provideHeater() {
  return new ElectricHeater();
}

 

@Provides註解的方法能夠有本身的依賴。在Pump被請求的時候,下面的方法返回了Thermosiphon的實例。

@Provides static Pump providePump(Thermosiphon pump) {
  return pump;
}

 

全部@Provides註解的方法必須屬於一個Module。下面就是用@Module註解的類。

@Module
class DripCoffeeModule {
  @Provides static Heater provideHeater() {
    return new ElectricHeater();
  }
 
  @Provides static Pump providePump(Thermosiphon pump) {
    return pump;
  }
}

 

按照慣例,@Provides註解的方法命名帶上provide前綴,@Module註解的類命名會帶上Module後綴。

 

創建對象圖

@Inject和@Provides註解的類經過依賴鏈接造成一副對象圖。像應用的main方法同樣調用代碼,或者像Android應用經過一套定義良好的根集合來訪問對象圖。在Dagger中,這個集合是經過一個聲明不帶參數、返回指定類型的方法的接口來定義的。在這樣的接口上添加@Component註解並給module參數指定Module類型,Dagger就會按照約定完整的生成一套組件實現。

@Component(modules = DripCoffeeModule.class)

interface CoffeeShop {

  CoffeeMaker maker();

}

 

這個組件實現會有和接口同樣的名字,可是會加上Dagger前綴。在這個組件實現上調用builder()方法,爲返回的builder設置依賴,再調用build()就能夠獲取一個對象實例。

CoffeeShop coffeeShop = DaggerCoffeeShop.builder()

    .dripCoffeeModule(new DripCoffeeModule())

    .build();

 

注意,若是你的@Component註解的類不是一個頂級類,生成的組件名將會包括如下劃線鏈接的外層嵌套類的名字,例以下面的代碼會生成名爲DaggerFoo_Bar_BazComponent.的組件。

class Foo {

  static class Bar {

    @Component

    interface BazComponent {}

  }

}

 

對於任何帶有可訪問的默認構造函數的module,Dagger都會在沒有設置過對象實例的時候自動建立它的實例。

對於全部的@Provides註解方法都是Static的module,這個組件實現就不須要對象實例了。若是全部的依賴都不須要經過開發者建立實例來構建,那麼生成的組件實現也會有一個create()用來獲取對象實例,而不須要使用builder。

CoffeeShop coffeeShop = DaggerCoffeeShop.create();

 

如今咱們的CoffeeApp能夠簡單的使用Dagger生成的CoffeeShop組件實現來獲取一個徹底注入的CoffeeMaker了。

public class CoffeeApp {
  public static void main(String[] args) {
    CoffeeShop coffeeShop = DaggerCoffeeShop.create();
    coffeeShop.maker().brew();
  }
}

 

如今對象圖已經被構建,入口也已經注入,咱們能夠運行CoffeeApp。

$ java -cp ... coffee.CoffeeApp
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[_]P coffee! [_]P

 

對象圖的綁定

上面的例子展現瞭如何用一些典型的綁定構建一個組件(Component),還有不少機制來完善對象圖的綁定。下面是一些能夠用來產生一個不錯的組件的依賴選擇。

  1. 那些用@Module註解聲明@Provides註解方法的類直接被@Component.module引用或者間接被@Module.module引用。
  2. 任何一個使用@Inject註解構造函數的沒有範圍限定的類型,或者有一個@Scope註解匹配組件的一個範圍限定的類型。
  3. 組件依賴的規定方法(Provision method,規定方法屬於Component,沒有參數,返回一個Inject或Provide的類型,也能夠帶qualifier)。
  4. 組件自身。
  5. 被包含的子組件的未限制(unqualified)的builders。
  6. 以上全部綁定的Provider或者Lazy包裝。
  7. 任何一種類型的成員注入器(MemberInjector,用於注入類字段)。

 

單例和Scope的綁定

用@Singleton註解@Provides方法或可注入的類,對象圖會爲全部的調用者使用一個單例對象(相對於Component的生命週期而非Application的生命週期)。

@Provides @Singleton static Heater provideHeater() {
  return new ElectricHeater();
}

 

在可注入類上註解@Singleton也有@Document的做用(用Document註解的註解默認會被javadoc或同類工具文檔化)。它會提醒維護者這個類會被多個線程分享。

@Singleton
class CoffeeMaker {
  ...
}

 

因爲Dagger在對象圖中關聯了Scope的類實例和組件實現的實例,組件自身須要聲明它們所表明的Scope。例如,將@Singleton綁定和@RequestScoped綁定放到一個組件中是沒有任何意義的,由於它們有不一樣的生命週期,應該放在不一樣生命週期的組件中。在組件接口上直接應用Scope註解就能夠聲明組件的scope

@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
  CoffeeMaker maker();
}

 

組件能夠應用多個scope註解,這代表它們都是同一個scope的別名,這樣組件就能夠用它聲明的任何一種scope來包含scope綁定。

###Reusable scope

有時候你可能想要限制@Inject註解的構造函數或@Provides註解的方法的調用次數,可是你不須要保證在組件或子組件的生命週期中使用的確實是相同的實例。這在像Android這樣分配內存代價昂貴的環境中是很是有用的。

對於這樣的綁定,你可使用@Reusable註解(Dagger2.3加入)。@Reusable綁定,不像其餘的Scope——它不和任何單獨的component關聯,而是實際使用綁定的component緩存返回或初始化的實例。

這意味着若是你在組件中引入一個有@Reusable綁定的模塊,可是隻有一個子組件實際用到這個綁定,那麼只有這個子組件會緩存這個綁定。若是共享祖先的兩個子組件各自使用到這個綁定,那它們各自都會緩存本身的對象。若是一個組件的祖先已經緩存了這個對象,子組件會直接使用它。

由於沒法保證組件只會調用這個綁定一次,因此應用@Reusable到返回易變對象的綁定中,或者必需要使用相同實例的對象上,是很危險的。若是不用關心實例化次數的話,在unscope對象上用@Reusable是安全的。

@Reusable // It doesn't matter how many scoopers we use, but don't waste them.
class CoffeeScooper {
  @Inject CoffeeScooper() {}
}
 
@Module
class CashRegisterModule {
  @Provides
  @Reusable // DON'T DO THIS! You do care which register you put your cash in.
            // Use a specific scope instead.
  static CashRegister badIdeaCashRegister() {
    return new CashRegister();
  }
}
 
  
@Reusable // DON'T DO THIS! You really do want a new filter each time, so this
          // should be unscoped.
class CoffeeFilter {
  @Inject CoffeeFilter() {}
}
 

 

Lazy injection

有時候你須要一個類被懶實例化,對於任何的綁定T,你能夠建立一個Lazy<T>來讓它懶實例化——直到第一次調用Lazy<T>.get()。若是T是一個單例,在對象圖中,對於全部的注入,Lazy<T>是同一個實例,不然,每個注入點都會有本身的Lazy<T>實例。不用管它,後續對任何Lazy<T>對象的調用都會返回相同的底層的T的實例。

class GridingCoffeeMaker {
  @Inject Lazy<Grinder> lazyGrinder;
 
  public void brew() {
    while (needsGrinding()) {
      // Grinder created once on first call to .get() and cached.
      lazyGrinder.get().grind();
    }
  }
}

 

 

Provider注入

有時候你須要不一樣的實例被返回而不是簡單的注入一個對象實例。當你有幾個選擇得時候(如工廠、建造器等),一個選擇是注入一個Provider<T>而不只僅是T。每一次.get()被調用的時候,Provider<T>都會調用綁定邏輯。若是那個綁定邏輯是一個@Inject構造函數,一個新的實例會被建立,而一個@Provides的方法沒法保證這樣。

class BigCoffeeMaker {
  @Inject Provider<Filter> filterProvider;
 
  public void brew(int numberOfPots) {
  ...
    for (int p = 0; p < numberOfPots; p++) {
      maker.addFilter(filterProvider.get()); //new filter every time.
      maker.addCoffee(...);
      maker.percolate();
      ...
    }
  }
}

 

注意:注入Provider<T>可能會建立讓人迷惑的代碼,而且你的對象圖設計裏可能會有做用域混亂或結構混亂的味道。你一般會想用工廠、Lazy<T>或者重組代碼的生命週期和結構的方式以便只須要注入一個T。然而,在某些狀況下使用Provider<T>將會拯救你:一個常見的用法是當你必須用一個遺留架構而不會增加對象的生命週期的時候(例如,Servlets被設計成單例,可是隻會在請求特定數據的上下文中有效)。

 

限定符

有時候單靠類型不足以說明依賴,例如,一個複雜的Coffee maker應用可能會須要不一樣的加熱器來加熱水和碟子。

在這種狀況下,咱們添加一個限定符註解。這是一個它自身有一個@Qualifier註解的註解。下面是@Named註解的聲明,它是一個包含在javax.inject中的限定符註解。

@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
  String value() default "";
}

 

你能夠建立本身的限定符註解或者直接使用@Named。用限定符來註解感興趣的字段或參數,類型和限定符註解都會被用來識別依賴。

class ExpensiveCoffeeMaker {

  @Inject @Named("water") Heater waterHeater;

  @Inject @Named("hot plate") Heater hotPlateHeater;

  ...

}

 

在相應的@Provides方法上註解限定值

@Provides @Named("hot plate") static Heater provideHotPlateHeater() {

  return new ElectricHeater(70);

}
@Provides @Named("water") static Heater provideWaterHeater() {

  return new ElectricHeater(93);

}

 

依賴不會有多個限定符註解。

編譯時驗證

Dagger的註解處理器是很是嚴格的,若是有任何無效或不完整的綁定,它會引發編譯錯誤。例如,下面這個被引入組件模塊,缺乏一個Executor的綁定。

@Module

class DripCoffeeModule {

  @Provides static Heater provideHeater(Executor executor) {

    return new CpuHeater(executor);

  }

}

 

編譯的時候,javac會拒絕缺乏的綁定

[ERROR] COMPILATION ERROR :

[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.

在組件的任何一個模塊中添加Executor的@Provides註解的方法能夠解決這個問題。雖然@Inject、@Module、@Provides都是單獨驗證的,全部綁定關係的驗證都是在Component層進行。Dagger1嚴格使用@Module級驗證(可不受運行時行爲影響),可是Dagger2省略了這種驗證(以及在@Module上配套的配置參數),更偏向於完整對象圖驗證。

編譯時代碼生成

Dagger的註解處理器(須要啓用註解處理器)也能夠生成相似以CoffeeMaker_Factory.java 或者CoffeeMaker_MembersInjector.java命名的源文件,這些文件是Dagger實現的細節,雖然他們在單步調試注入時很方便順手,你不須要直接使用它們。在代碼裏你應該使用的惟一文件是以Dagger爲前綴的組件文件。

 

多重綁定

Dagger容許你用多重綁定來把多個對象綁定到一個集合,即便這些對象已經綁定到不一樣的模塊。Dagger彙集了這個(依賴)集合,這樣應用代碼就能夠注入它,而不須要直接依賴單獨的綁定。

你能夠用多重綁定來實現一個插件架構。好比,多個模塊能夠貢獻不一樣的插件接口實現,中心類可使用整個插件集。或者可讓多個模塊貢獻不一樣的服務提供者到一個以名字爲Key的Map。

多重綁定不能用Set<Object>,Map<?,Object>之類的綁定。

<a name=set-multibindings></a> ## Set multibindings

要貢獻元素到一個可注入的多重綁定集合,在模塊中添加返回元素的方法,這個方法應該以@Provides(type=SET)註解。

@Module

class MyModuleA {

  @Provides(type = SET)

  static String provideOneString(DepA depA, DepB depB) {

    return "ABC";

  }

}

 

你也能夠經過在模塊中添加以@Provides(type=SET_VALUES)註解返回一個子集的方法來一次貢獻多個元素。

@Module

class MyModuleB {

  @Provides(type = SET_VALUES)

  static Set<String> provideSomeStrings(DepA depA, DepB depB) {

    return new HashSet<String>(Arrays.asList("DEF", "GHI"));

  }

}

 

這樣組件裏的綁定就能夠依賴這個集合:

class Bar {

  @Inject Bar(Set<String> strings) {

    assert strings.contains("ABC");

    assert strings.contains("DEF");

    assert strings.contains("GHI");

  }

}

 

或者組件能夠提供這個集合:

@Component(modules = {MyModuleA.class, MyModuleB.class})

interface MyComponent {

  Set<String> strings();

}

 

@Test void testMyComponent() {

  MyComponent myComponent = DaggerMyComponent.create();

  assertThat(myComponent.strings()).containsExactly("ABC", "DEF", "GHI");

}

 

對於其餘的綁定,除了能夠依賴多重綁定Set<Foo>,還能夠依賴Provider<Set<Foo>>和Lazy<Set<Foo>>,可是不能依賴Set<Provider<Foo>>[I1] 。

要貢獻一個有Qualifier的多重綁定集合,用qualifier來註解每個@Provides方法:

@Module

class MyModuleC {

  @Provides(type = SET)

  @MyQualifier

  static Foo provideOneFoo(DepA depA, DepB depB) {

    return new Foo(depA, depB);

  }

}

 

@Module

class MyModuleD {

  @Provides

  static FooSetUser provideFooSetUser(@MyQualifier Set<Foo> foos) { … }

}

 

<a name=map-multibindings></a> ## Map multibindings

 

Dagger可讓你用多重綁定來貢獻入口到一個可注入的Map,只要Map的Key在編譯時是知道的。

爲了貢獻入口到多重綁定Map,在模塊中添加以@Provides(type = MAP)註解返回入口值的方法,還要用一個自定義的註解來指定入口在Map中的Key。要貢獻一個入口到有限定的多重綁定Map中,用限定符註解每個@Provides(type = MAP)的方法。

而後你能夠注入Map自身(Map<K, V>),也能夠注入一個包含值Provider的Map (Map<K, Provider<V>>)。若是你想要一次取得一個值而不須要每一個值都初始化,或者你想要每次查詢Map的時候都獲得一個新的實例,那麼第二種方式頗有用。

簡單的Map keys

對於用字符串、Class<?>或者裝箱類型做爲key的map,在dagger.mapkeys中使用以下標準註解之一:

@Module
class MyModule {
  @Provides(type = MAP)
  @StringKey("foo")
  static Long provideFooValue() {
    return 100L;
  }
 
  @Provides(type = MAP)
  @ClassKey(Thing.class)
  static String provideThingValue() {
    return "value for Thing";
  }
}
 
@Component(modules = MyModule.class)
interface MyComponent {
  Map<String, Long> longsByString();
  Map<Class<?>, String> stringsByClass();
}
 
@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.longsByString().get("foo")).isEqualTo(100L);
  assertThat(myComponent.stringsByClass().get(Thing.class))
      .isEqualTo("value for Thing");
}

 

對於Key爲枚舉或者更特殊的參數化的類的Map,寫一個以@MapKey註解的註解類型,這個註解類應該包括一個Map key類型的成員:

enum MyEnum {
  ABC, DEF;
}
 
@MapKey
@interface MyEnumKey {
  MyEnum value();
}
 
@MapKey
@interface MyNumberClassKey {
  Class<? extends Number> value();
}
 
@Module
class MyModule {
  @Provides(type = MAP)
  @MyEnumKey(MyEnum.ABC)
  static String provideABCValue() {
    return "value for ABC";
  }
 
  @Provides(type = MAP)
  @MyNumberClassKey(BigDecimal.class)
  static String provideBigDecimalValue() {
    return "value for BigDecimal";
  }
}
 
@Component(modules = MyModule.class)
interface MyComponent {
  Map<MyEnum, String> myEnumStringMap();
  Map<Class<? extends Number>, String> stringsByNumberClass();
}
 
@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.myEnumStringMap().get(MyEnum.ABC)).isEqualTo("value for ABC");
  assertThat(myComponent.stringsByNumberClass.get(BigDecimal.class))
      .isEqualTo("value for BigDecimal");
}

 

註解的惟一成員能夠用除Arrays之外的任何有效的註解成員類型,還能夠取任意名稱。

 

複雜的Map keys

若是Map 的key很難用一個註解成員表達,你能夠設置@MapKey’s unwrapValue爲false以使用所有的註解做爲Map key,在這種狀況下,註解也能夠有數組元素。

@MapKey(unwrapValue = false)
@interface MyKey {
  String name();
  Class<?> implementingClass();
  int[] thresholds();
}
 
@Module
class MyModule {
  @Provides(type = MAP)
  @MyKey(name = "abc", implementingClass = Abc.class, thresholds = {1, 5, 10})
  static String provideAbc1510Value() {
    return "foo";
  }
}
 
@Component(modules = MyModule.class)
interface MyComponent {
  Map<MyKey, String> myKeyStringMap();
}

 

 

使用@AutoAnnotation 來建立註解實例

若是你的map用了複雜的keys,你須要在運行時建立@MapKey註解的實例並傳遞給map的get(Object)方法,最簡單的作法是用@AutoAnnotation註解建立一個實例化註解的靜態方法。更多細節能夠查看@AutoAnnotation的文檔。

class MyComponentTest {
  @Test void testMyComponent() {
    MyComponent myComponent = DaggerMyComponent.create();
    assertThat(myComponent.myKeyStringMap()
        .get(createMyKey("abc", Abc.class, new int[] {1, 5, 10}))
        .isEqualTo("foo");
  }
 
  @AutoAnnotation
  static MyKey createMyKey(String name, Class<?> implementingClass, int[] thresholds) {
    return new AutoAnnotation_MyComponentTest_createMyKey(name, implementingClass, thresholds);
  }
}

 

 

在編譯時不知道Keys的Map

多重綁定Map僅僅對在編譯時知道Key而且Key能夠用註解來表示的狀況有效。若是Map的keys不符合這個條件,就不能建立多重綁定Map。可是你能夠變通一下,用多重綁定集合來綁定一個對象集合,而後把它轉變成一個非多重綁定的Map:

@Module
class MyModule {
  @Provides(type = SET)
  static Map.Entry<Foo, Bar> entryOne(…) {
    Foo key = …;
    Bar value = …;
    return new SimpleImmutableEntry(key, value);
  }
 
  @Provides(type = SET)
  static Map.Entry<Foo, Bar> entryTwo(…) {
    Foo key = …;
    Bar value = …;
    return new SimpleImmutableEntry(key, value);
  }
}
 
@Module
class MyMapModule {
  @Provides
  static Map<Foo, Bar> fooBarMap(Set<Map.Entry<Foo, Bar>> entries) {
    Map<Foo, Bar> fooBarMap = new LinkedHashMap<>(entries.size());
    for (Map.Entry<Foo, Bar> entry : entries) {
      fooBarMap.put(entry.getKey(), entry.getValue());
    }
    return fooBarMap;
  }
}

 

注意,這個技術也不會自動綁定Map<Foo, Provider<Bar>>,若是想要一個包含Providers 的map,多重綁定集合中的Map.Entry對象就應該包括Providers,這樣,非多重綁定的Map就能夠有Provider值:

@Module
class MyModule {
  @Provides(type = SET)
  static Map.Entry<Foo, Provider<Bar>> entry(
      Provider<BarSubclass> barSubclassProvider) {
    Foo key = …;
    return new SimpleImmutableEntry(key, barSubclassProvider);
  }
}
 
@Module
class MyProviderMapModule {
  @Provides
  static Map<Foo, Provider<Bar>> fooBarProviderMap(
      Set<Map.Entry<Foo, Provider<Bar>>> entries) {
    return …;
  }
}
 

 

聲明多重綁定

能夠在一個模塊中嵌套一個@Multibindings註解的接口來聲明多重綁定Set或Map,這個接口應該包括返回你想要聲明的Set或Map的方法。

對於至少有一個元素的Set或Map使用@Multibindings沒有必要,若是它們可能爲空的話就必需要聲明。

@Module
class MyModule {
  @Multibindings
  interface MyMultibindings {
    Set<Foo> aSet();
    @MyQualifier Set<Foo> aQualifiedSet();
    Map<String, Foo> aMap();
    @MyQualifier Map<String, Foo> aQualifiedMap();
  }
}

 

接口上的全部方法和父類型(除了Object中的方法)都被用來聲明多重綁定,而接口的名字和方法會被忽略。一個Set或Map的多重綁定能夠被聲明無數次而且不會發生錯誤。Dagger不會實現這個接口或者調用它的方法。

### Alternative: SET_VALUES returning an empty set

單就空集合來講,做爲一種候選方式,你能夠添加一個@Provides(type = SET_VALUES)的方法來返回一個空集合:

@Module
class MyEmptySetModule {
  @Provides(type = SET_VALUES)
  static Set<Foo> primeEmptyFooSet() {
    return Collections.emptySet();
  }
}

 

子組件(繼承)的多重綁定

子組件的綁定能夠依賴於父類的多重綁定Set或Map,就像它能夠依賴父類的其它任意綁定同樣。它能夠經過在模塊中添加合適的@Provides方法來添加元素到父類的多重綁定集合或Map中。

這樣作以後,Set或Map會由於注入的地方而不一樣。當它注入到子組件定義的綁定時,就會同時具備父子組件定義的多重綁定的值或者入口了。當它注入到父類定義的綁定中時,就只會有父類定義的值或入口了。

@Component(modules = ParentModule.class)
interface ParentComponent {
  Set<String> strings();
  Map<String, String> stringMap();
  ChildComponent childComponent();
}
 
@Module
class ParentModule {
  @Provides(type = SET)
  static String string1() {
    "parent string 1";
  }
 
  @Provides(type = SET)
  static String string2() {
    "parent string 2";
  }
 
  @Provides(type = MAP)
  @StringKey("a")
  static String stringA() {
    "parent string A";
  }
 
  @Provides(type = MAP)
  @StringKey("b")
  static String stringB() {
    "parent string B";
  }
}
 
@Subcomponent(modules = ChildModule.class)
interface ChildComponent {
  Set<String> strings();
  Map<String, String> stringMap();
}
 
@Module
class ChildModule {
  @Provides(type = SET)
  static String string3() {
    "child string 3";
  }
 
  @Provides(type = SET)
  static String string4() {
    "child string 4";
  }
 
  @Provides(type = MAP)
  @StringKey("c")
  static String stringC() {
    "child string C";
  }
 
  @Provides(type = MAP)
  @StringKey("d")
  static String stringD() {
    "child string D";
  }
}
 
@Test void testMultibindings() {
  ParentComponent parentComponent = DaggerParentComponent.create();
  assertThat(parentComponent.strings()).containsExactly(
      "parent string 1", "parent string 2");
  assertThat(parentComponent.stringMap().keySet()).containsExactly("a", "b");
 
  ChildComponent childComponent = parentComponent.childComponent();
  assertThat(childComponent.strings()).containsExactly(
      "parent string 1", "parent string 2", "child string 3", "child string 4");
  assertThat(childComponent.stringMap().keySet()).containsExactly(
      "a", "b", "c", "d");
}

 

 

子組件

子組件是繼承和擴展父組件對象圖的組件。能夠用來把應用的對象圖分割成子圖,

這樣就能夠壓縮應用的各個部分,或者在一個組件上使用多個範圍限定。子組件中綁定的對象除了能夠依賴在它本身的模塊中綁定的對象,還能夠依賴父組件或上層組件中綁定的對象。反過來講,在父組件中綁定的對象不能依賴子組件中綁定的對象。固然,子組件中綁定的對象也不能依賴於同級子組件中綁定的對象。換句話說,父組件的對象圖是子組件對象圖的子圖。

聲明子組件

跟頂級組件同樣,你寫一個抽象類或接口,在裏面聲明返回應用關心的類型的抽象方法。這裏不使用@Component來註解它,而是使用@Subcomponent註解並引入須要的模塊。

@Subcomponent(modules = RequestModule.class)
inferface RequestComponent {
  RequestHandler requestHandler();
}

 

把子組件加入到父組件

要添加子組件到父組件中,在父組件中添加一個返回子組件的抽象工廠方法。若是子組件有一個沒有默認構造函數的Module,而且這個Module不會被引入父組件,那麼這個工廠方法必須有一個模塊類型的參數。這個抽象方法能夠有引入到子組件中的其餘Module類型的參數,可是不能夠有引入到父組件的模塊類型的參數(子組件會自動在它和它的父組件之間分享已經分享的模塊的實例)。

@Component(modules = {ServerModule.class, AuthModule.class})
interface ServerComponent {
  Server server();
  SessionComponent sessionComponent(SessionModule sessionModule);
}
 
@Subcomponent(modules = SessionModule.class)
interface SessionComponent {
  SessionInfo sessionInfo();
  RequestComponent requestComponent();
}
 
@Subcomponent(modules = {RequestModule.class, AuthModule.class})
interface RequestComponent {
  RequestHandler requestHandler();
}

 

SessionComponent的模塊中的綁定能夠依賴ServerComponent的模塊中的綁定,RequestComponent的模塊中的綁定能夠同時依賴SessionComponent和ServerComponent的模塊中的綁定。

爲了建立子組件的實例,調用父組件實例的工廠方法:

ServerComponent serverComponent = DaggerServerComponent.create();
SessionComponent sessionComponent =
    serverComponent.sessionComponent(new SessionModule(…));
RequestComponent requestComponent = sessionComponent.requestComponent();

 

一般你須要從父組件的一個對象範圍內建立子組件,你能夠基於組件的任何綁定都依賴於組件自身類型的事實來這樣作:

class BoundInServerComponent {
  @Inject ServerComponent serverComponent;
 
  void doSomethingWithSessionInfo() {
    SessionComponent sessionComponent =
        serverComponent.sessionComponent(new SessionModule(…));
    sessionComponent.sessionInfo().doSomething();
  }
}
 

 

子組件Builders

你也能夠爲子組件定義一個Buidler,和組件Buidler的定義類似:

@Component(modules = {ServerModule.class, AuthModule.class})
interface ServerComponent {
  Server server();
  SessionComponent.Builder sessionComponentBuilder();
}
 
@Subcomponent(modules = SessionModule.class)
interface SessionComponent {
  @Subcomponent.Builder
  interface Builder {
    Builder sessionModule(SessionModule sessionModule);
    SessionComponent build();
  }
}
 
ServerComponent serverComponent = DaggerServerComponent.create();
SessionComponent sessionComponent = serverComponent.sessionComponentBuilder()
    .sessionModule(new SessionModule(…))
    .build();

 

注入子組件Buidlers

和組件同樣,子組件Buidlers被綁定到對象圖而且也能夠被注入,因此能夠注入Buidlers自身,而不用注入組件而後在組件上調用子組件的builder方法。

/** Injecting the subcomponent builder. This is simpler than what's below. */
  class SessionStarterInjectingSubcomponentBuilder {
    private final SessionComponent.Builder sessionComponentBuilder;
    
    @Inject SessionStarterInjectingSubcomponentBuilder(
        SessionComponent.Builder sessionComponentBuilder) {
      this.sessionComponentBuilder = sessionComponentBuilder;
    }
    
    Session startSession() {
      return sessionComponentBuilder
          .sessionModule(new SessionModule(…))
          .build()
          .session();
    }
  }
 
  /**
   * Injecting the component and calling the factory method. Not as simple as
   * what's above.
   */
  class SessionStarterInjectingComponent {
    private final ServerComponent serverComponent;
    
    @Inject SessionStarterInjectingComponent(ServerComponent serverComponent) {
      this.serverComponent = serverComponent;
    }
 
    Session startSession() {
      return serverComponent.sessionComponentBuilder()
          .sessionModule(new SessionModule(…))
          .build()
          .session();
    }
  }

 

注意SessionStarterInjectingSubcomponentBuilder根本沒有提到ServerComponent。

 

子組件和範圍限定

把應用的組件分割成子組件的緣由之一是爲了使用範圍限定。對於普通的無範圍限定的綁定,每一個注入類型的使用者都會得到一個新的、獨立的實例。可是若是這個綁定加上了範圍限定,在範圍限定的生命週期內這個綁定的全部用戶都會得到綁定類型的同一實例。

標準的範圍限定是@Singleton,單例限定綁定的用戶會得到相同的實例(相對於Component的生命週期而非應用生命週期)。

在Dagger中,用@Scope註解組件就能夠加上範圍限定。在這種狀況下,組件實現會保存對全部加上範圍限定的類實例的引用以便重用。有範圍限定的帶有@Provides方法的模塊只能被擁有相同範圍限定的組件引用。

(有@Inject構造函數的類型也能夠用Scope註解,這種隱含綁定也會被這個Scope的組件和它的子組件使用。這個Scope實例會被綁定到正確的Scope裏。)

雖然兩個不能互相訪問的組件能夠關聯到相同的Scope上(由於存儲Scope對象的地方明確,雖然使用相同的範圍限定,兩個子組件會有不一樣的Scope實例),可是沒有子組件會關聯到和它祖先相同的Scope上。

例以下面的組件樹中,BadChildComponent有和它的父類RootComponent相同的@RootScope,這是錯的。可是SiblingComponentOne和SiblingComponentTwo均可以使用@ChildScope註解,由於兩者的綁定關係不會混亂:

@RootScope @Component
interface RootComponent {
  BadChildComponent badChildComponent(); // ERROR!
  SiblingComponentOne siblingComponentOne();
  SiblingComponentTwo siblingComponentTwo();
}
 
@RootScope @Subcomponent
interface BadChildComponent {…}
 
@ChildScope @Subcomponent
interface SiblingComponentOne {…}
 
@ChildScope @Subcomponent
interface SiblingComponentTwo {…}

 

由於子組件是經過調用父組件的方法來初始化的,它的生命週期會短於父組件。這意味着在子組件上關聯較小的範圍限定,在父組件上關聯較大的範圍限定纔有意義。實際上,你幾乎總會想讓RootComponent使用@Singleton的範圍限定。

在下面的Scope例子中, RootComponent使用了@Singleton。@SessionScope內嵌於@Singleton之中,@RequestScope內嵌於@SessionScope之中。注意FooRequestComponent和BarRequestComponent都和@RequestScope關聯(由於他們是同級,彼此沒有繼承關係)。

@Singleton @Component
interface RootComponent {
  SessionComponent sessionComponent();
}
 
@SessionScope @Subcomponent
interface SessionComponent {
  FooRequestComponent fooRequestComponent();
  BarRequestComponent barRequestComponent();
}
 
@RequestScope @Subcomponent
interface FooRequestComponent {…}
 
@RequestScope @Subcomponent
interface BarRequestComponent {…}
 

 

 

子組件封裝

使用子組件的另外一個緣由是能夠封裝應用內的各個部分。例如你服務器上的兩個服務(或者應用的兩個界面)共享一些綁定,假設用於受權和驗證,可是它們每一個都有彼此不相關的其餘綁定,這時爲每一個服務(或界面)建立單獨的子組件,並把共享的綁定放入父組件會比較有意義。

在上面的例子中,FooRequestComponent和BarRequestComponent是獨立的,同級的組件,你能夠把他們(和他們的模塊)放到一個@RequestScope的組件中,可是可能會有一些綁定衝突。

 

 

細節

擴展多重綁定

和其餘的綁定同樣,父組件中的多重綁定對於子組件是可見的,子組件也能夠添加多重綁定到父組件中綁定的Map和Set中。任何這種形式的附加綁定都只對子組件及它的子組件可見,而父組件不可見。

@Component(modules = ParentModule.class)
interface Parent {
  Map<String, Int> map();
  Set<String> set();
 
  Child child();
}
 
@Module
class ParentModule {
  @Provides(type = MAP)
  @StringKey("one") static int one() {
    return 1;
  }
 
  @Provides(type = MAP)
  @StringKey("two") static int two() {
    return 2;
  }
 
  @Provides(type = SET)
  static String a() {
    return "a"
  }
 
  @Provides(type = SET)
  static String b() {
    return "b"
  }
}
 
@Subcomponent(modules = Child.class)
interface Child {
  Map<String, String> map();
  Set<String> set();
}
 
@Module
class ChildModule {
  @Provides(type = MAP)
  @StringKey("three") static int three() {
    return 3;
  }
 
  @Provides(type = MAP)
  @StringKey("four") static int four() {
    return 4;
  }
 
  @Provides(type = SET)
  static String c() {
    return "c"
  }
 
  @Provides(type = SET)
  static String d() {
    return "d"
  }
}
 
Parent parent = DaggerParent.create();
Child child = parent.child();
assertThat(parent.map().keySet()).containsExactly("one", "two");
assertThat(child.map().keySet()).containsExactly("one", "two", "three", "four");
assertThat(parent.set()).containsExactly("a", "b");
assertThat(child.set()).containsExactly("a", "b", "c", "d");

 

 

重複的模塊

當相同的模塊類型被引入到同一個組件或者它的子組件中時,這些組件都會自動使用同一個模塊實例。這意味着若是子組件的工廠方法包括一個重複的模塊做爲參數或者你傳入一個重複的模塊來調用子組件的Builder方法是錯誤的(後者是一個運行時錯誤,在編譯時不能被檢查)。

@Component(modules = {RepeatedModule.class, …})
interface ComponentOne {
  ComponentTwo componentTwo(RepeatedModule repeatedModule); // COMPILE ERROR!
  ComponentThree.Builder componentThreeBuilder();
}
 
@Subcomponent(modules = {RepeatedModule.class, …})
interface ComponentTwo { … }
 
@Subcomponent(modules = {RepeatedModule.class, …})
interface ComponentThree {
  @Subcomponent.Builder
  interface Builder {
    Builder repeatedModule(RepeatedModule repeatedModule);
    ComponentThree build();
  }
}
 
DaggerComponentOne.create().componentThreeBuilder()
    .repeatedModule(new RepeatedModule()) // UnsupportedOperationException!
    .build();
 

 

Producers

Producers是Dagger的擴展,它實現了Java中的異步注入。

概述

假設你已經熟悉了Dagger的API和Guava 的 ListenableFuture。

Producers引入了新的註解@ProducerModule, @Produces, 和@ProductionComponent,分別對應@Module,@Provides, 和 @Component。咱們把@ProducerModule註解的類視爲producer modules,@Produces註解的方法視爲producer methods,@ProductionComponent註解的接口視爲producer graphs(相似於modules, provider methods, 和object graphs)。

和原始Dagger最關鍵的不一樣在於producer方法會返回ListenableFuture<T>,而且後面的producer會依賴於這個未封裝的T,當future可用的時候,框架會計劃執行後續的方法。

下面是一個模擬服務器處理請求工做流的例子:

@ProducerModule(includes = UserModule.class)

final class UserResponseModule {

  @Produces

  static ListenableFuture<UserData> lookUpUserData(

      User user, UserDataStub stub) {

    return stub.lookUpData(user);

  }

 

  @Produces

  static Html renderHtml(UserData data, UserHtmlTemplate template) {

    return template.render(data);

  }

}

 

@Module

final class ExecutorModule {

  @Provides

  @Production

  static Executor executor() {

    return Executors.newCachedThreadPool();

  }

}

 

 

在下面的例子中,咱們描述的算法是:

  1. 調用lookUpUserData方法。
  2. 當future可用的時候,獲取結果,調用renderHtml方法。

(注意咱們沒有明確的指出User, UserDataStub, 或者UserHtmlTemplate是怎麼來的,假設(Dagger module )UserModule 提供了這些類型。)

咱們能夠經過綁定@Production Executor來指定一個線程池,每個Producer 的方法都會在這個線程池中被計劃執行。注意,即便指定executor的provides方法是unscoped的,production component也只會從這個provider獲取一個實例,因此只會建立一個線程池。

咱們用了一個ProductionComponent來創建圖:

@ProductionComponent(modules = UserResponseModule.class)
interface UserResponseComponent {
  ListenableFuture<Html> html();
}
 
// ...
 
UserResponseComponent component = DaggerUserResponseComponent.create();
 
ListenableFuture<Html> htmlFuture = component.html();

 

Dagger生成UserResponseComponent的實現,它的html()方法的行爲和上面描述的同樣:首先調用lookUpUserData,當future可用的時候,調用renderHtml()

兩個producer 方法都在提供的線程池上被計劃執行,因此執行模型徹底是用戶指定的。

注意,就像在上面的例子中,producer modules能夠和原生module無縫結合,可是有一點兒限制: provided types不能依賴於produced types。

異常處理

默認狀況下,若是producer method拋出了異常,或者它返回future失敗,那麼全部有依賴關係的producer methods將會被跳過——這個模型在調用棧中向上傳遞異常。若是producer method想要捕獲這個異常,它能夠請求一個Produced<T>而不是T。例如:

@Produces
static Html renderHtml(
    Produced<UserData> data,
    UserHtmlTemplate template,
    ErrorHtmlTemplate errorTemplate) {
  try {
    return template.render(data.get());
  } catch (ExecutionException e) {
    return errorTemplate.render("user data failed", e.getCause());
  }
}

 

在這個例子中,若是產生UserData拋出了異常(能夠是producer method拋出異常,或者獲取future失敗),那麼readHtml方法會捕獲它並返回一個錯誤模板。

若是沒有producer method捕獲,異常會一直向上傳遞到組件的入口點,那麼component返回的future就會因異常而失敗。

 

延遲注入

Producer methods能夠請求一個相似於Provider<T>的Producer<T>,它能夠延遲關聯綁定計算直到get()方法被調用。Producer<T>不會阻塞,它的get()方法返回一個後面能夠反饋給框架的ListenableFuture,例如:

@Produces
static ListenableFuture<UserData> lookUpUserData(
    Flags flags,
    @Standard Producer<UserData> standardUserData,
    @Experimental Producer<UserData> experimentalUserData) {
  return flags.useExperimentalUserData()
      ? experimentalUserData.get()
      : standardUserData.get();
}

 

 

在這個例子裏,若是experimental user data被請求,那麼standard user data不會被計算。注意Flags多是一個請求時間標識,或者甚至是RPC((Remote Procedure Call Protocol)——遠程過程調用協議)的結果,讓用戶構建靈活的條件圖。

多重綁定

和普通的Dagger同樣,同一類型的多個綁定能夠被整理到一個Set或者Map中。例如:

@ProducerModule
final class UserDataModule {
  @Produces(type = SET) static ListenableFuture<Data> standardData(…) { … }
  @Produces(type = SET) static ListenableFuture<Data> extraData(…) { … }
  @Produces(type = SET) static Data synchronousData(…) { … }
  @Produces(type = SET_VALUES) static Set<ListenableFuture<Data>> rest(…) { … }
 
  @Produces static … collect(Set<Data> data) { … }
}

 

在這個例子中,當貢獻到Set的全部producer methods都有完成的futures的時候,Set<Data>會被建立,並調用collect()方法。

 

Map多重綁定

和Set多重綁定類似:

@MapKey @interface DispatchPath {
  String value();
}
 
@ProducerModule
final class DispatchModule {
  @Produces(type = MAP) @DispatchPath("/user")
  static ListenableFuture<Html> dispatchUser(…) { … }
 
  @Produces(type = MAP) @DispatchPath("/settings")
  static ListenableFuture<Html> dispatchSettings(…) { … }
 
  @Produces
  static ListenableFuture<Html> dispatch(
      Map<DispatchPath, Producer<Html>> dispatchers, Url url) {
    return dispatchers.get(url.path()).get();
  }
}

 

注意,dispatch()請求Map<DispatchPath, Producer<Html>>的方式確保了只有被請求的dispatch handler會被執行。

 

 

Scoping(範圍限定)

Producer methods 和production components 已經被隱含的Scope上了@ProductionScope,和普通的Scoped bindings同樣,在組件的上下文中每一個方法只會執行一次,而且它的結果會被緩存。這樣就能夠徹底控制每一個綁定的生存時間——和它內嵌的component實例的生存時間同樣。

@ProductionScope也能夠被應用到普通的provisions上,這樣它們就會被Scope到綁定它們的Production component上。Production components也能夠像普通的components同樣擁有其餘的Scope。

Executor

提供Executor的主要方式是在ProductionComponent 或ProductionSubcomponent中綁定@Production Executor。這個綁定將會隱含的Scope到@ProductionScope。對於subcomponents,executor能夠被綁定到任何父component,而且綁定會被子組件繼承(和全部的綁定同樣)。

 

組件依賴

和普通的 Components同樣, ProductionComponents 可能會依賴於其餘的接口:

interface RequestComponent {
  ListenableFuture<Request> request();
}
 
@ProducerModule
final class UserDataModule {
  @Produces static ListenableFuture<UserData> userData(Request request, …) { … }
}
 
@ProductionComponent(
    modules = UserDataModule.class,
    dependencies = RequestComponent.class)
interface UserDataComponent {
  ListenableFuture<UserData> userData();
}

 

由於UserDataComponent 依賴於RequestComponent,綁定UserDataComponent的時候,Dagger會要求提供一個RequestComponent的實例,而後這個實例會用來知足它提供的getter方法的綁定:

ListenableFuture<UserData> userData = DaggerUserDataComponent.builder()
    .requestComponent(/* a particular RequestComponent */)
    .build()
    .userData();
 

 

Subcomponents(子組件)

Dagger producers引入了一個對應於@Subcomponent的新註解@ProductionSubcomponent,Production subcomponents能夠是components或production components的子組件。

子組件會繼承父組件的全部綁定,因此建.立嵌套Scope是最簡單的方式。能夠參考子組件介紹。

 

Monitoring(監控)

ProducerMonitor能夠被用來監視producer methods的執行,它的方法對應producer生命週期的不一樣階段。

要引入ProducerMonitor,須要貢獻到一個ProductionComponentMonitor.Factory的集合綁定中。例如:

@Module
final class MyMonitorModule {
  @Provides(type = SET)
  static ProductionComponentMonitor.Factory provideMonitorFactory(
      MyProductionComponentMonitor.Factory monitorFactory) {
    return monitorFactory;
  }
}
 
@ProductionComponent(modules = {MyMonitorModule.class, MyProducerModule.class})
interface MyComponent {
  ListenableFuture<SomeType> someType();
}

 

當這個component被建立的時候,貢獻到集合中的每個monitor factory都會被要求爲這個component建立一個monitor,在component的生存期會保存生成的實例(一個),這個實例會被用來爲每一個producer method建立獨立的monitor。

Timing, Logging and Debugging

到2016.3月,尚未實現。

 

測試Dagger

使用Dagger這樣的依賴注入框架的好處之一是可讓代碼測試更加簡單。下面談到一些使用到Dagger的應用的測試策略。

別用Dagger作單元測試

若是你想寫一個小的單元測試來測試一個@Inject註解的類,你根本不須要用到Dagger,直接調用@Inject註解的構造方法、方法,設置@Inject的字段(若是有的話),直接傳遞僞造或模擬的依賴。

final class ThingDoer {
  private final ThingGetter getter;
  private final ThingPutter putter;
 
  @Inject ThingDoer(ThingGetter getter, ThingPutter putter) {
    this.getter = getter;
    this.putter = putter;
  }
 
  String doTheThing(int howManyTimes) { /**/ }
}
 
public class ThingDoerTest {
  @Test
  public void testDoTheThing() {
    ThingDoer doer = new ThingDoer(fakeGetter, fakePutter);
    assertEquals("done", doer.doTheThing(5));
  }
}

 

Replace bindings for functional/integration/end-to-end testing

功能、集成、端到端測試一般用在產品型應用上,在替換存儲、後端和驗證系統以後,讓剩下的部分正常運行。這種方式讓應用自身有一個(或者有限個數)測試配置,能夠替換其中的某些綁定。

Option 1: Override bindings by subclassing modules (don’t do this!)

替換綁定最簡單的方式是在子類中重寫module的@Provides方法(可是看看下面的問題)。

當你建立Dagger component的實例時,會傳遞它用到的modules(你不須要傳遞有無參構造函數或沒有實例方法的module的實例,可是你能夠這樣作)。這意味着你能夠傳遞這些模塊的子類實例,而且這些子類能夠重寫@Provides方法來替換一些綁定。

@Component(modules = {AuthModule.class, /**/})
interface MyApplicationComponent { /**/ }
 
@Module
class AuthModule {
  @Provides AuthManager authManager(AuthManagerImpl impl) {
    return impl;
  }
}
 
class FakeAuthModule extends AuthModule {
  @Override
  AuthManager authManager(AuthManagerImpl impl) {
    return new FakeAuthManager();
  }
}
 
MyApplicationComponent testingComponent = DaggerMyApplicationComponent.builder()
    .authModule(new FakeAuthModule())
    .build();

 

 

可是這種方法有一些限制:

1.使用module子類不能改變綁定圖的靜態形狀:它不能添加或移除綁定,或者改變綁定依賴。特別是:

(1)重寫@Provides方法不能改變它的參數類型,而且限制了返回類型(可能在綁定圖上沒有意義)。在上面的例子中,testingComponent仍然要求AuthManagerImpl和它全部依賴的綁定,即便它們沒有用處。

 

(2)相似的,重寫的module不能添加綁定到圖上,包括新的多重綁定貢獻(雖然你能夠重寫SET_VALUES方法來返回不一樣的集合)。子類中任何新的@Provides方法會被Dagger靜默忽略。實際上,這意味着你的模擬不能充分利用依賴注入的優點。

2.用這種方式重寫的@Provides方法不能是靜態的,因此它們的module實例不能被省略。

 

### Option 2: Separate component configurations

另外一種方式要求應用更加前瞻性的設計Module。應用(產品和測試)的每一個配置用一個不一樣的component配置。測試的component類型擴展產品的component類型而且引用一個不一樣的modules集合。

@Component(modules = {
  OAuthModule.class, // real auth
  FooServiceModule.class, // real backend
  OtherApplicationModule.class,
  /**/ })
interface ProductionComponent {
  Server server();
}
 
@Component(modules = {
  FakeAuthModule.class, // fake auth
  FakeFooServiceModule.class, // fake backend
  OtherApplicationModule.class,
  /**/})
interface TestComponent extends ProductionComponent {
  FakeAuthManager fakeAuthManager();
  FakeFooService fakeFooService();
}

 

如今測試代碼的主方法調用DaggerTestComponent.builder()而不是DaggerProductionComponent.builder()。注意Test component接口能夠添加provision handles到僞造的實例(fakeAuthManager() 和fakeFooService())上,這樣測試在必要的時候就能夠訪問他們來控制繮繩(控制他們)。

可是你如何設計modules才能讓這種模式簡單一點兒呢?

 

組織Module提升可測試性

Module類是一種實用類:一個獨立的@Provides方法集,它們每一個都會被注入器用來提供應用使用到的類型(實例)。

(在一個依賴於其餘類型的Module中,雖然多個@Provides 方法是相關聯的,他們一般不會互相調用或者依賴一樣的易變狀態。在實際並不獨立的狀況下,一些@Provides方法確實用到一樣的實例字段,建議把@Provides方法當成實用方法對待,由於它能夠很容易替換modules來作測試)

那麼你如何決定哪些@Provides方法應該一塊兒放入一個module呢?

一種方式認爲能夠對綁定分爲published bindings和internal bindings,而後再更多考慮哪個發行綁定有合理的替代(alternatives)。

published bindings提供功能供應用其餘部分使用。像AuthManager、User和DocDatabase這樣的類型都是published bindings:它們被綁定到一個module以便於應用的其餘部分使用它們。

internal bindings就是除了published bindings剩下的部分:在一些published的類型的實現中使用而且不會被看成它的一部分的綁定。例如,OAuth client ID和OAuthKeyStore的綁定配置只趨向於讓AuthManager的OAuth實現使用,而不讓應用的剩餘部分使用。這些綁定一般是Package-private的類型或者使用package-private限定符限定了。

一些published bindings會有reasonable alternatives,特別是爲了測試。好比,可能會存在針對某種類型(像AuthManager)的替代綁定:一種是用於測試,其它的用於不一樣的驗證、受權協議。

可是另外一方面,若是AuthManager接口有一個返回當前登陸的user的方法,你可能想在AuthManager中publish一個經過簡單的調用getCurrentUser()來提供user的綁定。這個published綁定不可能須要一個alternative。

一旦你已經把綁定分類成爲有reasonable alternatives的published bindings、無reasonable alternatives的published bindings和internal bindings,考慮像下面這樣把他們放到module中:

1.每個有reasonable alternative的published bindings做爲一個module(若是你也在寫alternatives,讓它們都有本身的module)。這個module準確的包含一個published binding,和它須要的全部internal bindings。

2.全部沒有reasonable alternatives的published bindings都放到按功能劃分的modules中。

3. published-binding modules應該包括(include)要求public bindings提供的no-reasonable-alternative modules。[I2] 

以描述module提供的published bindings的方式把它文檔化是一個不錯的選擇。

下面是一個使用auth domain的例子。若是存在一個AuthManager接口,就能夠有OAuth的實現和一個僞造實現用於測試。如上所述,對於當前user會有一個你不想在配置切換時改變的明確綁定。

/**
 * Provides auth bindings that will not change in different auth configurations,
 * such as the current user.
 */
@Module
class AuthModule {
  @Provides static User currentUser(AuthManager authManager) {
    return authManager.currentUser();
  }
  // Other bindings that don’t differ among AuthManager implementations.
}
 
/** Provides a {@link AuthManager} that uses OAuth. */
@Module(includes = AuthModule.class) // Include no-alternative bindings.
class OAuthModule {
  @Provides static AuthManager authManager(OAuthManager authManager) {
    return authManager;
  }
  // Other bindings used only by OAuthManager.
}
 
/** Provides a fake {@link AuthManager} for testing. */
@Module(includes = AuthModule.class) // Include no-alternative bindings.
class FakeAuthModule {
  @Provides static AuthManager authManager(FakeAuthManager authManager) {
    return authManager;
  }
  // Other bindings used only by FakeAuthManager.
}

 

像上面描述的,這樣你的production configuration會使用真實的modules,testing configuration使用僞造的modules。

 

下一篇文章,我將介紹如何在Android開發中應用Dagger2。

 

參考:

Martin fowler論依賴注入:http://www.martinfowler.com/articles/injection.html

依賴注入: https://en.wikipedia.org/wiki/Dependency_injection

相關文章
相關標籤/搜索