Dagger2是第一个使用生成代码的方式实现依赖注入的框架。作为Dagger的升级版本,自然有它的优势,优先注重的是执行效率。本文着重介绍Dagger2。官方据点传送门: https://google.github.io/dagger//users-guide.html
首先来看一下依赖注入和控制反转
在软件工程领域,DI是一种实现控制反转用来解决依赖的设计模式,依赖是一个可以被使用的对象(服务),注入是把依赖传递给依赖它的对象(客户),即要使用它的对象,这样,服务就成了客户组成的一部分。传递服务到客户,而不是让客户创建或寻找服务,是这个模式的基本要求。
依赖注入允许程序设计遵循依赖倒置原则,客户把为它提供依赖的责任委托给外部代码(依赖注入器),它自身不允许使用注入器的代码,而是注入器创建服务并把他们注入到客户。这意味着客户不需要知道注入器代码,不需要知道如何创建服务,不需要知道自己使用的具体是什么服务,而只需要知道如何使用服务的接口定义。这就分离了创建和使用的关系。
客户接受依赖注入有三种方式:
Setter和构造函数注入的方式主要看什么时候会使用到依赖,接口注入方式在于依赖可以控制自己的注入。三者都需要独立的注入代码来负责引入客户和依赖之间的相互关系。
控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。引用51CTO的一张图:
实现控制反转主要有两种方式:依赖注入和依赖查找。两者的区别在于,前者是被动的接收对象,在类A的实例创建过程中即创建了依赖的B对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取响应名称的对象,获得依赖对象的时间也可以在代码中自由控制。
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在下面情形时无能为力:
在这些@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),还有很多机制来完善对象图的绑定。下面是一些可以用来产生一个不错的组件的依赖选择。
用@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() {} }
有时候你需要一个类被懒实例化,对于任何的绑定T,你可以创建一个Lazy
class GridingCoffeeMaker { @Inject LazylazyGrinder; public void brew() { while (needsGrinding()) { // Grinder created once on first call to .get() and cached. lazyGrinder.get().grind(); } } }
有时候你需要不同的实例被返回而不是简单的注入一个对象实例。当你有几个选择得时候(如工厂、建造器等),一个选择是注入一个Provider
class BigCoffeeMaker { @Inject ProviderfilterProvider; 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
有时候单靠类型不足以说明依赖,例如,一个复杂的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
要贡献元素到一个可注入的多重绑定集合,在模块中添加返回元素的方法,这个方法应该以@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 SetprovideSomeStrings(DepA depA, DepB depB) { return new HashSet (Arrays.asList("DEF", "GHI")); } }
这样组件里的绑定就可以依赖这个集合:
class Bar { @Inject Bar(Setstrings) { assert strings.contains("ABC"); assert strings.contains("DEF"); assert strings.contains("GHI"); } }
或者组件可以提供这个集合:
@Component(modules = {MyModuleA.class, MyModuleB.class}) interface MyComponent { Setstrings(); } @Test void testMyComponent() { MyComponent myComponent = DaggerMyComponent.create(); assertThat(myComponent.strings()).containsExactly("ABC", "DEF", "GHI"); }
对于其他的绑定,除了可以依赖多重绑定Set
要贡献一个有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 Setfoos) { … } } ## Map multibindings
Dagger可以让你用多重绑定来贡献入口到一个可注入的Map,只要Map的Key在编译时是知道的。
为了贡献入口到多重绑定Map,在模块中添加以@Provides(type = MAP)注解返回入口值的方法,还要用一个自定义的注解来指定入口在Map中的Key。要贡献一个入口到有限定的多重绑定Map中,用限定符注解每一个@Provides(type = MAP)的方法。
然后你可以注入Map自身(Map
对于用字符串、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 { MaplongsByString(); Map , 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 { MapmyEnumStringMap(); Map 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 的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 { MapmyKeyStringMap(); }
使用@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); } }
多重绑定Map仅仅对在编译时知道Key并且Key可以用注解来表示的情况有效。如果Map的keys不符合这个条件,就不能创建多重绑定Map。但是你可以变通一下,用多重绑定集合来绑定一个对象集合,然后把它转变成一个非多重绑定的Map:
@Module class MyModule { @Provides(type = SET) static Map.EntryentryOne(…) { Foo key = …; Bar value = …; return new SimpleImmutableEntry(key, value); } @Provides(type = SET) static Map.Entry entryTwo(…) { Foo key = …; Bar value = …; return new SimpleImmutableEntry(key, value); } } @Module class MyMapModule { @Provides static Map fooBarMap(Set > entries) { Map fooBarMap = new LinkedHashMap<>(entries.size()); for (Map.Entry entry : entries) { fooBarMap.put(entry.getKey(), entry.getValue()); } return fooBarMap; } }
注意,这个技术也不会自动绑定Map
@Module class MyModule { @Provides(type = SET) static Map.Entry> entry( Provider barSubclassProvider) { Foo key = …; return new SimpleImmutableEntry(key, barSubclassProvider); } } @Module class MyProviderMapModule { @Provides static Map > fooBarProviderMap( Set >> entries) { return …; } }
可以在一个模块中嵌套一个@Multibindings注解的接口来声明多重绑定Set或Map,这个接口应该包括返回你想要声明的Set或Map的方法。
对于至少有一个元素的Set或Map使用@Multibindings没有必要,如果它们可能为空的话就必须要声明。
@Module class MyModule { @Multibindings interface MyMultibindings { SetaSet(); @MyQualifier Set aQualifiedSet(); Map aMap(); @MyQualifier Map aQualifiedMap(); } }
接口上的所有方法和父类型(除了Object中的方法)都被用来声明多重绑定,而接口的名字和方法会被忽略。一个Set或Map的多重绑定可以被声明无数次并且不会发生错误。Dagger不会实现这个接口或者调用它的方法。
### Alternative: SET_VALUES returning an empty set
单就空集合来说,作为一种候选方式,你可以添加一个@Provides(type = SET_VALUES)的方法来返回一个空集合:
@Module class MyEmptySetModule { @Provides(type = SET_VALUES) static SetprimeEmptyFooSet() { return Collections.emptySet(); } }
子组件的绑定可以依赖于父类的多重绑定Set或Map,就像它可以依赖父类的其它任意绑定一样。它可以通过在模块中添加合适的@Provides方法来添加元素到父类的多重绑定集合或Map中。
这样做之后,Set或Map会因为注入的地方而不同。当它注入到子组件定义的绑定时,就会同时具有父子组件定义的多重绑定的值或者入口了。当它注入到父类定义的绑定中时,就只会有父类定义的值或入口了。
@Component(modules = ParentModule.class) interface ParentComponent { Setstrings(); Map 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 strings(); Map 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(); } }
你也可以为子组件定义一个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 { Mapmap(); Set 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 map(); Set 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是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
下面是一个模拟服务器处理请求工作流的例子:
@ProducerModule(includes = UserModule.class) final class UserResponseModule { @Produces static ListenableFuturelookUpUserData( 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(); } }
在下面的例子中,我们描述的算法是:
(注意我们没有明确的指出User, UserDataStub, 或者UserHtmlTemplate是怎么来的,假设(Dagger module )UserModule 提供了这些类型。)
我们可以通过绑定@Production Executor来指定一个线程池,每一个Producer 的方法都会在这个线程池中被计划执行。注意,即使指定executor的provides方法是unscoped的,production component也只会从这个provider获取一个实例,所以只会创建一个线程池。
我们用了一个ProductionComponent来建立图:
@ProductionComponent(modules = UserResponseModule.class) interface UserResponseComponent { ListenableFuture html(); } // ... UserResponseComponent component = DaggerUserResponseComponent.create(); ListenableFuture 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
@Produces static Html renderHtml( Produceddata, 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
@Produces static ListenableFuturelookUpUserData( Flags flags, @Standard Producer standardUserData, @Experimental Producer 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 standardData(…) { … } @Produces(type = SET) static ListenableFuture extraData(…) { … } @Produces(type = SET) static Data synchronousData(…) { … } @Produces(type = SET_VALUES) static Set> rest(…) { … } @Produces static … collect(Set data) { … } }
在这个例子中,当贡献到Set的所有producer methods都有完成的futures的时候,Set会被创建,并调用collect()方法。
Map多重绑定
和Set多重绑定相似:
@MapKey @interface DispatchPath { String value(); } @ProducerModule final class DispatchModule { @Produces(type = MAP) @DispatchPath("/user") static ListenableFuture dispatchUser(…) { … } @Produces(type = MAP) @DispatchPath("/settings") static ListenableFuture dispatchSettings(…) { … } @Produces static ListenableFuture dispatch( Map> dispatchers, Url url) { return dispatchers.get(url.path()).get(); } }
注意,dispatch()请求Map
Producer methods 和production components 已经被隐含的Scope上了@ProductionScope,和普通的Scoped bindings一样,在组件的上下文中每个方法只会执行一次,并且它的结果会被缓存。这样就可以完全控制每个绑定的生存时间——和它内嵌的component实例的生存时间一样。
@ProductionScope也可以被应用到普通的provisions上,这样它们就会被Scope到绑定它们的Production component上。Production components也可以像普通的components一样拥有其他的Scope。
提供Executor的主要方式是在ProductionComponent 或ProductionSubcomponent中绑定@Production Executor。这个绑定将会隐含的Scope到@ProductionScope。对于subcomponents,executor可以被绑定到任何父component,并且绑定会被子组件继承(和所有的绑定一样)。
和普通的 Components一样, ProductionComponents 可能会依赖于其他的接口:
interface RequestComponent { ListenableFuturerequest(); } @ProducerModule final class UserDataModule { @Produces static ListenableFuture userData(Request request, …) { … } } @ProductionComponent( modules = UserDataModule.class, dependencies = RequestComponent.class) interface UserDataComponent { ListenableFuture userData(); }
因为UserDataComponent 依赖于RequestComponent,绑定UserDataComponent的时候,Dagger会要求提供一个RequestComponent的实例,然后这个实例会用来满足它提供的getter方法的绑定:
ListenableFutureuserData = DaggerUserDataComponent.builder() .requestComponent(/* a particular RequestComponent */) .build() .userData();
Dagger producers引入了一个对应于@Subcomponent的新注解@ProductionSubcomponent,Production subcomponents可以是components或production components的子组件。
子组件会继承父组件的所有绑定,所以建.立嵌套Scope是最简单的方式。可以参考子组件介绍。
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 { ListenableFuturesomeType(); }
当这个component被创建的时候,贡献到集合中的每一个monitor factory都会被要求为这个component创建一个monitor,在component的生存期会保存生成的实例(一个),这个实例会被用来为每个producer method创建独立的monitor。
到2016.3月,还没有实现。
使用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)); } }
功能、集成、端到端测试通常用在产品型应用上,在替换存储、后端和验证系统之后,让剩下的部分正常运行。这种方式让应用自身有一个(或者有限个数)测试配置,可以替换其中的某些绑定。
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类是一种实用类:一个独立的@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