官方介绍翻译,原文:dagger
使用 Dagger
用一个例子来说明 Dagger 的依赖关系注入方法。完整的代码可以编译执行,见coffee example.
声明依赖
Dagger可以构造应用需要的实例和它们的依赖关系。使用 javax.inject.Inject 注解来标注哪个构造器和字段是它感兴趣的部分。
Dagger应该在创建一个类实例时使用@Inject方法注解构造器。当一个新的实例被请求构建时,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 会使用默认的无参构造函数(如果存在)。缺少@Inject注解的类不能被 Dagger 构造。
Dagger 不支持方法的注入。
满足依赖
默认情况下,Dagger 通过给的参数构造实例来满足每个依赖,如上所述。当你请求一个 CoffeeMaker, 它会获取一个实例通过调用 new CoffeeMaker() 并设置它的可注入字段。
但是 @Inject 并不是万能的:
*接口不能被构造
*第三方的类不能被注解
*配置类需要配置
在这些情况下 @Inject 就显得力不从心。使用 @Provides 注解方法来满足这些依赖关系。方法的返回类型定义了它所满足的依赖关系。
例如,provideHeater() 会在获取 Heater 时调用:
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides 注解的方法也可以和它们自身有依赖关系。这个例子会在 Pump 被请求时返回一个 Thermosiphon:
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
所有的 @Provides 方法都必须属于一个模块。模块只是被 @Module 注解的类。
@Module
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Pump providePump(Thermosiphon pump) {
return pump;
}
}
为了方便统一,@Provides 注解的方法加 provide 前缀,模块类会加 Module 后缀。
构建图表
@Inject 和 @Provides 注解的类共同构成了整个项目图表,联系是它们之间的依赖关系。调用 ObjectGraph.create() 来获取这个图表,这个图表接受一个或多个模块:
1 ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());
为了让图表投入使用我们需要引导注入(Bootstrap injection)。通常在入口类中添加一行调用注入的命令,或者是 Android 的 activity 类里。在这个 coffee 的例子里,CoffeeApp 类是用来初始化注入依赖的。向图表请求一个有注入的实例:
class CoffeeApp implements Runnable {
@Inject CoffeeMaker coffeeMaker;
@Override public void run() {
coffeeMaker.brew();
}
public static void main(String[] args) {
ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());
CoffeeApp coffeeApp = objectGraph.get(CoffeeApp.class);
...
}
}
现在还剩的一件事就是 CoffeeApp 还不为图表所知。我们需要显式地在注册它,作为一个 @Module 注解的注入类型中:
@Module(
injects = CoffeeApp.class
)
class DripCoffeeModule {
...
}
injects 选项允许在编译时完成图表。这样使得问题的检测更早,开发更快,减少了使用反射的风险。
现在,图表已经构造完成,根对象也注入完成,可以运行咖啡制造应用了。
Fun.
$ java -cp ... coffee.CoffeeApp
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[_]P coffee! [_]P
单例
使用 @Singleton 注解一个 @Provides 方法或者可注入的类。图表就会对其使用单例模式。
@Provides @Singleton Heater provideHeater() {
return new ElectricHeater();
}
@Singleton 注解在一个可注入的类前也隐式地告知使用者这个类可能会在多线程中被使用。
@Singleton
class CoffeeMaker {
...
}
延迟注入
有时可能会需要延迟获取一个实例。对任何绑定的 T,可以构建一个 Lazy<T> 来延迟实例化直至第一次调用 Lazy<T> 的 get() 方法。如果 T 是一个单例模式,那么 Lazy<T> 会获取 ObjectGraph 中注入的相同实例。否则的话,每个注入都会获取单独的 Lazy<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。Provider<T> 在每次 .get() 方法调用时都会生成新的实例。
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();
...
}
}
}
Note:Provider<T> 有可能会创建混乱的代码,也有可能令图表审查错误或构造错误。许多时候你将使用 Factory<T> 或者 Lazy<T> 来重新构建代码的架构和生命周期,作为使用 Provider<T> 的保障。常用的情况是当你需要使用和整个对象原始的生命周期无关的结构时使用这个注入。
限定注入
有时单类型不足以定义依赖关系。例如,复杂的咖啡制造机应用也许希望分开加热水和盘子。
这个情况下,增加一个限定注解(qualifier annotation)。 这里是 @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") Heater provideHotPlateHeater() {
return new ElectricHeater(70);
}
@Provides @Named("water") Heater provideWaterHeater() {
return new ElectricHeater(93);
}
依赖关系也许并不会有多种限定注解
静态注入
<span style="color:red">警告:这个注入需要慎重使用,因为静态依赖关系难以检测和重用。</span>
Dagger 可以注入静态字段。有使用@Inject 注解静态字段的类,必须在模块注解中填充 staticInjections 参数。
@Module(
staticInjections = LegacyCoffeeUtils.class
)
class LegacyModule {
}
使用 ObjectGraph.injectStatics() 方法给这些静态字段填充注入值:
ObjectGraph objectGraph = ObjectGraph.create(new LegacyModule());
objectGraph.injectStatics();
注意:静态注入仅运行在即时图表模块。如果你在一个 plus() 方法创建的图表中调用了 injectStatics() 方法,那么在扩展扩展的图表模块中静态注入将不会执行。
编译时验证
Dagger 包含了一个注解处理器( annotation processor)来验证模块和注入。这个过程很严格而且会抛出错误,当有非法绑定或绑定不成功时。下面这个例子缺少了 Executor:
@Module
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}
当编译时,javac 会拒绝绑定缺少的部分:
[ERROR] COMPILATION ERROR :
[ERROR] error: No binding for java.util.concurrent.Executor
required by provideHeater(java.util.concurrent.Executor)
可以通过给方法 Executor 添加 @Provides注解来解决这个问题,或者标记这个模块是不完整的。不完整的模块允许缺少依赖关系。
@Module(complete = false)
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}
模块提供的方法中,若有注解中列出的注入类不需要的方法,也会报错。
@Module(injects = Example.class)
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Chiller provideChiller() {
return new ElectricChiller();
}
}
因为 Example 注入仅仅使用 Heater, javac 会拒绝未使用的绑定:
[ERROR] COMPILATION ERROR:
[ERROR]: Graph validation failed: You have these unused @Provider methods:
1. coffee.DripCoffeeModule.provideChiller()
Set library=true in your module to disable this check.
如果模块的绑定将在列出的注入以外的地方使用,那么就标记这个模块为 library。
@Module(
injects = Example.class,
library = true
)
class DripCoffeeModule {
@Provides Heater provideHeater() {
return new ElectricHeater();
}
@Provides Chiller provideChiller() {
return new ElectricChiller();
}
}
为了获得最全面的编译时验证,可以创建一个包含了所有模块的模块。这样注解处理器就会检测所有的模块的所有的问题然后给出报告。
@Module(
includes = {
DripCoffeeModule.class,
ExecutorModule.class
}
)
public class CoffeeAppModule {
}
在编译 classpath 中包含 Dagger 的 jar 文件,注解处理器就自动可用了。
编译时代码生成
Dagger 的注解处理器也许会生成源文件例如 CoffeeMaker$InjectAdapter.java 或者 DripCoffeeModule$ModuleAdapter。这些文件是 Dagger 的履行细节。不要直接使用它们,尽管它们可以执行单步调试。
模块重写
Dagger 会在有多个 @Provides 方法描述一个依赖关系时报错,但总有一些时候是需要替换产品代码来进行开发或测试。使用 overrides = true,就可以更换绑定。
这个单例测试重写了 DripCoffeeModule 的 Heater ,替换为 Mockito。这个测试类获取注入并用于测试。
public class CoffeeMakerTest {
@Inject CoffeeMaker coffeeMaker;
@Inject Heater heater;
@Before public void setUp() {
ObjectGraph.create(new TestModule()).inject(this);
}
@Module(
includes = DripCoffeeModule.class,
injects = CoffeeMakerTest.class,
overrides = true
)
static class TestModule {
@Provides @Singleton Heater provideHeater() {
return Mockito.mock(Heater.class);
}
}
@Test public void testHeaterIsTurnedOnAndThenOff() {
Mockito.when(heater.isHot()).thenReturn(true);
coffeeMaker.brew();
Mockito.verify(heater, Mockito.times(1)).on();
Mockito.verify(heater, Mockito.times(1)).off();
}
}
重写对于应用的小变动最合适不过:
替换真实的实现为模拟的用于单元测试
替换LDAP认证为假的认证协议,用于开发
对于更多更大量的变化,通常会选择使用不同的模块组合而非重写。