Dagger2是在Dagger1的基础上升级开发的,所以要学习Dagger2,先了解Dagger1。下文是由Dagger1的官方文档翻译而来。
参考:
原文链接
Dagger1项目链接
在任何应用中最好的类是那些“干活卖力”的:如BarcodeDecoder
,KoopaPhysicsEngine
和AudioStreamer
.这些类持有依赖,可能是一个BarcodeCameraFinder
,DefaultPhysicEngine
和HttpStreamer
.
相反的,应用中最不好的类是那些“尸位素餐”的家伙:BarcodeDecoderFactory
,CameraServiceLoader
和MutableContextWrapper
.这些类是和好东西缠绕在一块的管道胶带(These classes are the clumsy duct tape that wires the interesting stuff together)。
Dagger是工厂类的替代者(译者注:工厂类指的是负责创建对象的一些类)。它让你将精力花在感兴趣的类上面(译者注:即”关注点分离”)。声明依赖,指定如何去满足他们,然后主导(ship)你的app.
在javax.inject注解标准上构建,每个类都很容易测试。你不会因为一个FakeCreditCardService
要用一堆样板文件去替换RpcCreditCardService
.
依赖注入不仅仅是为了测试。它也会使得创建可复用、可替换的模块变得容易。你可以在你的app的所有地方共享一个AuthenticationModule
。并且你可以在开发期间运行DevLoggingModule
,而在产品阶段运行ProdLoggingModule
,从而在每个场景下获得正确的行为。
要了解更多信息,请观看QCon公司的Jesse Wilson 2012年的介绍性演讲。
我们将通过构建一个咖啡机的例子来演示依赖注入和Dagger.完整的可编译可执行代码,请参见Dagger的咖啡案例
Dagger构造你应用类的实例,并满足(satisfy)他们的依赖关系。它使用javax.inject.Inject
注解识别它感兴趣的构造方法或者字段(fields)。
使用@Inject
注解Dagger要用来创建类实例的构造方法。需要一个新实例时,Dagger会获取需要的参数值并调用构造器。
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
...
}
Dagger可以直接注入字段。例中,对于heater
字段和pump
字段它分别获取一个Heater
实例和一个Pump
实例。
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
...
}
如果你的类中有@Inject
标注的字段,但没有@Inject
标注的构造器,Dagger会试图使用一个无参构造器(如果存在)。缺少@Inject
标注的类不能使用Dagger构造。
Dagger不支持方法注入。
默认情况下,如上文描述那样,Dagger通过构造请求类型的实例满足每个依赖。当你请求一个CoffeeMaker
时,它会通过调用new CoffeeMaker()
获取一个,然后设置它的可注入字段。
但是,@Inject
并不是在任何地方都有效:
对于这些@Inject
不足或不合适的地方,我们使用一个由@Provides
标注的方法去满足依赖。该方法的返回类型定义了它要满足的依赖。
例如,在任何需要Heater
的时候调用provideHeater()
:
@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()
获取这张图,它可以接受一到多个模块(作为参数):
ObjectGraph objectGraph = ObjectGraph.create(new DripCoffeeModule());
为了将这张图用起来,我们需要引导注入。这通常需要注入到命令行APP的主类,或者Android App的Activity类中。在我们的咖啡案例中,使用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
项允许在编译时验证整张图。提早检测问题提高了开发速度,并在重构中提出了一些危险源。
既然图已经创建好了,根对象已经注入,我们可以运行我们的咖啡机app了
$ java -cp ... coffee.CoffeeApp
~ ~ ~ heating ~ ~ ~
=> => pumping => =>
[_]P coffee! [_]P
使用@Singleton
标注一个@Provides
方法或者可注入类(injectable class)。对象图将会为其所有使用者提供该值的一个单例。
@Provides @Singleton Heater provideHeater() {
return new ElectricHeater();
}
可注入类的@Singleton
注解也用作文档。它提示潜在的维护者该类可以在多线程中共享。
@Singleton
class CoffeeMaker {
...
}
有时候我们需要对象在使用时实例化。对于任何绑定T
,你可以创建一个Lazy
,它将实例化推迟到Lazy
的get()
方法的第一次调用。如果T
是一个单例,Lazy
则为ObjectGraph
范围内所有注解提供相同实例。否则,每个注入位(injection site)将得到其专属的Lazy
实例。无论如何,任何给定的Lazy
实例的调用将返回相同的T
底层实例。
class GridingCoffeeMaker {
@Inject Lazy lazyGrinder;
public void brew() {
while (needsGrinding()) {
// Grinder created once on first call to .get() and cached.
lazyGrinder.get().grind();
}
}
}
有时候你需要返回多个实例,而不仅仅是注入的那单独的一个值。当你需要多项(Factories,Builders等等)时,有一个办法是注入一个Provider
,而不是T
.一个Provider
每次调用.get()
都会创建一个新实例。
class BigCoffeeMaker {
@Inject Provider 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
创建的代码可能会让人费解,而且这种设计可能让你的对象图中充满了无范围、无组织的对象( 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. 译者注:smell of 应该是一种形容,像空气一样无处不在)。通常你要使用一个Factory
或一个Lazy
,或者重组你的代码的生命期(lifetimes)和结构,使之能够仅仅注入一个T
.但是在某些情况下,注入Provider
可以很省事( Injecting Provider
can, however, be a life saver in some cases )。常见用法是当你必须使用一个遗留架构(legacy architecture),它与你的对象的自然生命期不同步(例如servlets 设计成单例,但只在请求指定数据的环境中有效)。
有时候单纯的类型不能确定一个依赖。比如一个复杂的咖啡机应用可能需要为水和加热板分别提供加热器。
在这种情况下,我们添加一个qualifier annotation。任何使用@Qualifier
(译者注:元注解)注解的注解都属于这类。这里有一个@Named
的声明,就是javax.inject
内置的限定符注解。
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}
你可以创建你自己的限定符注解,或者直接使用@Named
.通过注解关注的字段或参数应用限定符。他们的类型和限定符注解都将用来标识(identify)依赖。
class ExpensiveCoffeeMaker {
@Inject @Named("water") Heater waterHeater;
@Inject @Named("hot plate") Heater hotPlateHeater;
...
}
通过注解相应的@Provides
方法提供合格值(qualified values)。
@Provides @Named("hot plate") Heater provideHotPlateHeater() {
return new ElectricHeater(70);
}
@Provides @Named("water") Heater provideWaterHeater() {
return new ElectricHeater(93);
}
依赖不可以同时使用多个限定符注解(Dependencies may not have multiple qualifier annotations)。
==警告:== 此功能应该有限使用,因为静态依赖难以测试和复用。
Dagger 可以注入静态字段。声明了使用@Inject
注解的静态字段的类必须列入module注解的staticInjections
.
@Module(
staticInjections = LegacyCoffeeUtils.class
)
class LegacyModule {
}
使用ObjectGraph.injectStatics()
给这些静态字段填充值:
ObjectGraph objectGraph = ObjectGraph.create(new LegacyModule());
objectGraph.injectStatics();
==注意:== 静态注入只适用于直接图中的模块。如果从一个plus()
调用图中的injectStatics()
,模块上的静态注入在扩展图中将不会发生作用(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 inthe extended graph will not be performed)。
Dagger包含一个验证(validates)模块和注入的注解处理器(annotaion 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
添加一个@Provide
方法或者将该模块标记为未完成(incomplete)可以修复这个问题。我们允许未完成模块缺失依赖项。
@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.
如果你的模块的绑定在注入类以外会被使用到,就请将该module标记为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 {
}
当你在编译路径中包含了Dagger的jar文件,注解处理器自动生效。
Dagger的注解处理器可以生成像CoffeeMaker$InjectAdapter.java
或者DripCoffeeModule$ModuleAdapter
这样的源文件。这些文件是Dagger的实现细节。你不需要直接使用它们,尽管通过注入进行步骤调试时会接触到它们(though they can be handy when step-debugging through an injection)。
如果多个有竞争关系的提供相同依赖的@Provides
方法,Dagger会因为错误导致失败。但是有时候使用开发或测试代码替代产品代码是必要的。在模块注解中使用override = true
让你获得相对于其它模块绑定的优先权。
这里Junit测试使用一个来自Mockito的模拟对象覆盖了DripCoffeeModule
对Heater
的绑定。这个模拟对象注入到CoffeeMaker
,然后进入测试。
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();
}
}
重写最适合应用中的小变动:
对于更大的变动,通常使用不同模块的组合更简单。
Dagger不支持Guice某些引人注意的特性:
final
字段和private
成员。为了Dagger生成代码的最佳性能。可使用构造函数注入来解决这一问题。EagerSingletons
类可解决这一问题。@Inject
注解的类,即使他们有一个无参构造函数。