单例(Singletons)
给@Provides注释的函数或者可注入的类, 添加注释@Singlton, 构建的这个对象图表将使用唯一的对象实例。
@Provides @Singleton Heater provideHeater() { return new ElectricHeater(); }
可注入类的@Singleton注释也可以作为说明文档, 它提示那些潜在的维护者们:多个线程将共享唯一个对象实例。
@Singleton class CoffeeMaker { ... }
译者:
@Singleton 注释对Dagger有效, 也只在一个ObjectGraph中生效。 若是有多个ObjectGraph, 则有多个相应的@Singleton对象。
延迟注入
某些情况下需要延迟初始化一个对象。对任意的对象T来说, 你可以使用Lazy实现延迟初始化。Lazy只有当Lazy's get()函数调用时, 才会初始化T对象。如果T是个单例的对象, Lazy也将使用同一个对象进行注入操作。否则,每次注入都将生成自己的 Lazy对象。 当然, 任何随后调用Lazy.get()函数将返回之前构建好的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 INJECTIONS)
有些情况下, 你需要多个对象实例, 而不是仅仅注入一个对象实例。这时你可以利用Provider实现, 每次调用Provider的get()函数将返回新的T的对象实例。
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(); ... } } }
Dagger作者也提醒:
注入Provider可能使代码易令人混淆, 是代码结构问题的体现, 请参考原文:
Note: Injecting Provider has the possibility of creating confusing code, and may be a design smell of mis-scoped or mis-structured objects in your graph. Often you will want to use a Factory or a Lazy or re-organize the lifetimes and structure of your code to be able to just inject a T. Injecting Provider can, however, be a life saver in some cases. A common use is when you must use a legacy architecture that doesn't line up with your object's natural lifetimes (e.g. servlets are singletons by design, but only are valid in the context of request-specfic data).
限定符(QUALIFIERS)
有些时候,单纯类型是不能够满足指定依赖的需求的。例如,一个复杂的Coffee maker程序可能需要不同的加热器。
在这种情况下,我们可以添加限定符注释. 这种注释本身有一个@Qualifier注释。 下面是javax.inject中@Named的声明代码:
@Qualifier @Documented @Retention(RUNTIME) public @interface Named { String value() default ""; }
你也可以创建自己的限定符注释, 也可以直接使用@Named, 给相应的成员变量添加限定符。这些限定符注释将与类型一起标记依赖关系。
class ExpensiveCoffeeMaker { @Inject @Named("water") Heater waterHeater; @Inject @Named("hot plate") Heater hotPlateHeater; ... } Supply qualified values by annotating the corresponding @Provides method. @Provides @Named("hot plate") Heater provideHotPlateHeater() { return new ElectricHeater(70); } @Provides @Named("water") Heater provideWaterHeater() { return new ElectricHeater(93); }
依赖关系也可以同时有多重限定符注释。
静态注入(STATIC INJECTION)
警告!!!
建议谨慎使用这个特性, 因为静态依赖注入很难测试和复用。
Dagger可以注入静态变量。拥有@Inject静态变量的类必须在@Module的staticInjections中明确说明。
@Module( staticInjections = LegacyCoffeeUtils.class ) class LegacyModule { }
可以使用ObjectGraph.injectStatics()注入静态变量:
ObjectGraph objectGraph = ObjectGraph.create(new LegacyModule()); objectGraph.injectStatics();
Note: Static injection only operates for modules in the immediate graph. If you call injectStatics() on a graph created from a call to plus(), static injections on modules in the extended graph will not be performed.
译者:
建议大家不要使用静态注入, 如Dagger所说, 静态类和静态变量非常难测试和维护。
即便不用Dagger,正常开发时, 也尽量少用静态变量静态函数, 这货看起来都丑!
编译时有效性检查(COMPILE-TIME VALIDATION)
Dagger包含一个annotation 处理器, 这个处理器检查module和注入的有效性。处理器非常严格, 若是有任何绑定是无效或者不完整的, 将引发编译错误。例如, 这个Module缺少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为不完整的。不完整的Module允许缺少对象引用。
@Module(complete = false) class DripCoffeeModule { @Provides Heater provideHeater(Executor executor) { return new CpuHeater(executor); } }
在Module中若provide的类,没有在injects列表中使用, 将在编译时触发错误。
@Module(injects = Example.class) class DripCoffeeModule { @Provides Heater provideHeater() { return new ElectricHeater(); } @Provides Chiller provideChiller() { return new ElectricChiller(); } }
因为Eample.class的注入声明在Module中仅使用了Heater, 而没有使用Chiller, 因此将触发未使用绑定错误:
[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.
若是这个Module提供的对象绑定, 可能被injects列表中以外的类使用, 可以将改Module标记为library, 以避免出错。
@Module( injects = Example.class, library = true ) class DripCoffeeModule { @Provides Heater provideHeater() { return new ElectricHeater(); } @Provides Chiller provideChiller() { return new ElectricChiller(); } }
为了在编译时检查所有的Module, 可以创建一个Module包含所有的程序Modules, annotation处理器将检测问题, 并报告。
@Module( includes = { DripCoffeeModule.class, ExecutorModule.class } ) public class CoffeeAppModule { }
当你将Dagger's jar文件包含到编译classpath中时, Annotation处理器自动运行。
编译时代码生成(COMPILE-TIME CODE GENERATION)
Dagger的Annotation processor 可以生成源码文件, 例如CoffeeMaker$InjectAdapter.java o, DripCoffeeModule$ModuleAdapter. 这些文件是Dagger的实现细节。你没有必要直接使用这些类文件, 但可以通过这些进行调试。
Module重载(MODULE OVERRIDES)
若对同一个依赖关系有多个@Provides函数, Dagger 将会报错。但在有些情况下,是有必要替换production代码的, 比如测试和开发。 在@Module中可以使用overrides =true , 重载其绑定关系。
下面这个JUnit测试利用Mockito, 重载了DripCoffeeModule的Heater绑定关系。这个Mock对象将inject到CoffeeMake中。
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(); } }
这种重载方式也很适合程序的小型变动, 例如付费版,免费版。
Replacing the real implementation with a mock for unit tests.
Replacing LDAP authentication with fake authentication for development.
For more substantial variations it's often simpler to use a different combination of modules.