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

相关文章
相关标签/搜索