Dagger 官方文档之Dagger1(译文)
来源:互联网 发布:软件质量评价模型 编辑:程序博客网 时间:2024/04/27 16:50
译者注
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的咖啡案例
声明依赖
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
标注的类。
@Moduleclass DripCoffeeModule { @Provides Heater provideHeater() { return new ElectricHeater(); } @Provides Pump providePump(Thermosiphon pump) { return pump; }}
按照惯例,@Provides
方法名以provide
为前缀,模块类名以module
为后缀。
建图(Building the Graph)
@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
单例(Singletons)
使用@Singleton
标注一个@Provides
方法或者可注入类(injectable class)。对象图将会为其所有使用者提供该值的一个单例。
@Provides @Singleton Heater provideHeater() { return new ElectricHeater();}
可注入类的@Singleton
注解也用作文档。它提示潜在的维护者该类可以在多线程中共享。
@Singletonclass CoffeeMaker { ...}
懒注解(Lazy Injections)
有时候我们需要对象在使用时实例化。对于任何绑定T
,你可以创建一个Lazy<T>
,它将实例化推迟到Lazy<T>
的get()
方法的第一次调用。如果T
是一个单例,Lazy<T>
则为ObjectGraph
范围内所有注解提供相同实例。否则,每个注入位(injection site)将得到其专属的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(); } }}
提供者注解(Providing Injections)
有时候你需要返回多个实例,而不仅仅是注入的那单独的一个值。当你需要多项(Factories,Builders等等)时,有一个办法是注入一个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(); ... } }}
==注意:== 注入Provider<T>
创建的代码可能会让人费解,而且这种设计可能让你的对象图中充满了无范围、无组织的对象( Injecting Provider<T>
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<T>
或一个Lazy<T>
,或者重组你的代码的生命期(lifetimes)和结构,使之能够仅仅注入一个T
.但是在某些情况下,注入Provider<T>
可以很省事( Injecting Provider<T>
can, however, be a life saver in some cases )。常见用法是当你必须使用一个遗留架构(legacy architecture),它与你的对象的自然生命期不同步(例如servlets 设计成单例,但只在请求指定数据的环境中有效)。
限定符(Qualifiers)
有时候单纯的类型不能确定一个依赖。比如一个复杂的咖啡机应用可能需要为水和加热板分别提供加热器。
在这种情况下,我们添加一个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)。
静态注解(Static Injection)
==警告:== 此功能应该有限使用,因为静态依赖难以测试和复用。
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
的绑定。
@Moduleclass 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文件,注解处理器自动生效。
编译时代码生成(Compile-time Code Generation)
Dagger的注解处理器可以生成像CoffeeMaker$InjectAdapter.java
或者DripCoffeeModule$ModuleAdapter
这样的源文件。这些文件是Dagger的实现细节。你不需要直接使用它们,尽管通过注入进行步骤调试时会接触到它们(though they can be handy when step-debugging through an injection)。
模块重写(Module Overrides)
如果多个有竞争关系的提供相同依赖的@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(); }}
重写最适合应用中的小变动:
- 在单元测试中使用一个模拟对象代替真实实现。
- 在开发中用一个假认证代替LDAP认证。
对于更大的变动,通常使用不同模块的组合更简单。
下载(略)
来自Guice的升级(Upgrading from Guice)
Dagger不支持Guice某些引人注意的特性:
- 注入
final
字段和private
成员。为了Dagger生成代码的最佳性能。可使用构造函数注入来解决这一问题。 - 饿汉单例(Eager Singletons)。为每个饿汉单例声明静态字段创建一个
EagerSingletons
类可解决这一问题。 - 方法注入。
- Dagger不能构造缺少
@Inject
注解的类,即使他们有一个无参构造函数。
参与(Contributing, 略)
证书(License, 略)
- Dagger 官方文档之Dagger1(译文)
- PreferenceActivity 安卓官方文档(译文)
- Pixi官方文档译文(1)
- Pixi官方文档译文(2)
- MediaCodec官方文档译文
- RecyclerView API官方文档译文
- Core Data Features(Apple官方文档译文)
- Core Data Basics(Apple官方文档的译文)
- App开发架构指南(谷歌官方文档译文)
- 十分钟搞定pandas(官方学习文档的译文)
- Activity官方文档译文简析
- 16.1 Python 描述符官方文档译文
- Exoplayer Developer guide官方文档译文
- reactor官方文档译文(1)Reactor简介
- (apple官方文档译文)ios应用开发当中的表视图
- iOS内存管理高级指南(官方文档——译文)
- android官方文档译文, 同步更新中(百度阅读免费领取)
- iOS开发-Swift4译文Swift4官方文档中文版/ Basic Operators(上)(转载)
- android 点击按钮打开浏览器网页
- TeamViewer13中文版下载附使用教程
- 盘点今年马斯克关于AI和机器人的观点 AI比朝鲜危险 | 观点
- Codeforces Round #453 (Div. 1)解题报告(ABCD)
- 人工智能的“军备竞赛”已经开启!未来一万年的人类是啥样? | 精选
- Dagger 官方文档之Dagger1(译文)
- 如果你要读一本真正普及“人工智能”的读物 | 赠书
- 2017亚马逊人工智能奖公布:他们的AI有什么不同? | 行业
- 运城达内java毕业生分享如何提高网站优化效率
- 【jquery】手风琴思路
- 远程链接Ubuntu16.04的MySQL5.7数据库
- 如何使用ABBYY FineReader 14自动处理文档
- VisionPro--toolblock添加用户脚本
- spring-boot-devtools热部署