Dagger2 User's Guide(翻译)
Posted shangmingchao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dagger2 User's Guide(翻译)相关的知识,希望对你有一定的参考价值。
概述
依赖注入(dependency injection)是一个对象为另一个对象提供依赖关系的技术手段。简单点说,就是一个对象(client)要依赖其它对象(services)才能完成工作,那么这个对象(client)就对其它对象(services)产生了依赖,而依赖注入就是把依赖(services)在需要的时候自动传给client,而不是client自己创建或者寻找services。也就是说客户对象(client)把提供依赖的职责交给了外部代码(注入器),注入器(injector)的注入代码会构建services并调用client注入依赖,client不用知道注入代码是什么,不用去构建services依赖对象,也不用知道真正用到的是什么service对象,只需要知道要帮它完成工作的service接口即可。这样就把使用和构建的职责分离了,达到了松耦合的作用,遵循依赖倒置和单一职责原则。
Dagger是当前比较流行的依赖注入框架,最先由square/dagger开发,不过由于注入过程中使用了反射机制会影响性能等等问题被Google fork并进行改进优化,所以有了更加优秀的Dagger2。
Dagger2是Java/android平台下的完全静态的、编译时的依赖注入框架。Dagger2的实现完全是自动生成代码(我们本应该手写的指定依赖关系的代码),以保证依赖注入尽可能的简单、可跟踪、高性能。依赖注入不单是为了利于测试,还是为了更容易地创建可重用的、可替换的Module。
使用
定义依赖(Declaring Dependencies)
Dagger,就像它的名字一样(Directed Acyclic Graph)将对象间的依赖关系表示成有向无环图。如果想要把类实例的构建交给Dagger处理,只需要在构造器上加上@Inject注解,Dagger会在需要的时候获取构造参数并调用构造器。
class Thermosiphon implements Pump {
private final Heater heater;
@Inject
Thermosiphon(Heater heater) {
this.heater = heater;
}
...
}
class CoffeeMaker {
@Inject Heater heater;
@Inject Pump pump;
...
}
Dagger可以直接对字段进行注入,将Heater实例和Pump实例注入给CoffeeMaker的字段。如果一个类有@Inject注解的字段但没有@Inject注解构造器,那么Dagger会在在需要的时候注入字段而不会创建新的实例,用@Inject注解无参构造器可以告诉Dagger可以构建该类的实例。缺少@Inject注解的类将不会被Dagger构建。
满足依赖(Satisfying Dependencies)
@Inject注解某些情况下是不可行的:
- 接口是不能构造的
- 第三方类不能添加注解
- Configurable对象必须configured
这些情况下,就需要@Module注解和@Provides注解来描述依赖关系了。
@Module注解的类表明它可以提供依赖对象,Module类会包含一系列用@Provides注解的@Provides方法,而@Provides方法的返回值就是依赖对象(也就是依赖图中的节点)。
@Module
class DripCoffeeModule {
@Provides static Heater provideHeater() {
return new ElectricHeater();
}
@Provides static Pump providePump(Thermosiphon pump) {
return pump;
}
}
按照惯例,@Provides方法以provide开头,Module类以Module结尾。
构建对象依赖图(Building the Graph)
@Inject和@Provides注解的类构成了对象依赖图,而应用可以通过一个接口访问这张依赖图,这个接口包含一系列返回值是依赖类型的无参方法。这个接口需要用@Component注解并给modules参数赋值:
@Component(modules = DripCoffeeModule.class)
interface CoffeeShop {
CoffeeMaker maker();
}
Dagger2会自动生成该接口的实现类(以Dagger开头,如果@Component注解类不是顶级类,自动生成的实现类的名字会闭包命名并用下划线分割如DaggerFoo_Bar_BazComponent),可以通过该实现类的builder()方法获得builder对象,builder对象可以设置好依赖,最后通过build()方法构建实例:
CoffeeShop coffeeShop = DaggerCoffeeShop.builder()
.dripCoffeeModule(new DripCoffeeModule())
.build();
如果Modules都是缺省构造器,@Provides方法都是静态的,用户不需要构建依赖实例,那么自动生成的实现类就会有create()方法获取新实例,就不需要处理builder了。
public class CoffeeApp {
public static void main(String[] args) {
CoffeeShop coffeeShop = DaggerCoffeeShop.create();
coffeeShop.maker().brew();
}
}
单例及作用域绑定(Singletons and Scoped Bindings)
@Singleton注解的provider方法或可注入类,对象依赖图将为其所有client提供一个唯一实例(单例)。@Singleton也表明该类可以被多个线程共享:
@Provides @Singleton static Heater provideHeater() {
return new ElectricHeater();
}
@Singleton
class CoffeeMaker {
...
}
Dagger2要为component实现类实例 关联 对象依赖图的作用域实例,以便可以更好地控制依赖类实例和client实例的生命周期,因此component自己就需要声明他想关联哪个作用域。同一个component同时拥有@Singleton绑定和@RequestScoped绑定是没意义的,因为这些作用域生命周期不一样。要想给component关联某个作用域,只需要为component添加scope注解即可:
@Component(modules = DripCoffeeModule.class)
@Singleton
interface CoffeeShop {
CoffeeMaker maker();
}
可重用的作用域绑定(Reusable scope)
如果你想控制@Inject构造器类被实例化或provider方法被调用的的次数,且不用保证某个component或subcomponent在生命周期中使用同一个实例,就可以使用@Reusable作用域了。@Reusable作用域绑定,不像其他作用域绑定一样需要关联一个component,它不会关联任何component,相反真正要用这个绑定的component都会缓存这个返回值或对象实例。
这就意味着,如果你用@Reusable为component指定module,且只有一个subcomponent会用到这个绑定,那么只有这个subcomponent会缓存这个绑定对象。如果两个subcomponent祖先没有共享一个绑定,那么每个subcomponent会各自缓存自己的对象。如果一个component的祖先已经缓存了绑定对象,那么subcomponent会重用这个对象。
由于无法保证component只会调用一次绑定,@Reusable绑定可能会返回不确定的对象,这个时候想引用同一个对象是很危险的。所以当你不关心对象会被实例化多少次使用@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() {}
}
可释放的引用(Releasable references)
如果一个绑定使用scope注解,这就意味着component对象会持有这个作用域对象的引用直到component自己被GC回收。在像Android这样的内存敏感环境中,如果你想在内存紧张时让GC回收当前未使用的作用域对象,只需要使用@CanReleaseReferences定义一个scope即可:
@Documented
@Retention(RUNTIME)
@CanReleaseReferences
@Scope
public @interface MyScope {}
可以为scope注入一个ReleasableReferenceManager对象,在内存紧张时调用它的releaseStrongReferences()方法以便让component持有该对象的弱引用而不是强引用。
@Inject @ForReleasableReferences(MyScope.class)
ReleasableReferences myScopeReferences;
void lowMemory() {
myScopeReferences.releaseStrongReferences();
}
当内存压力减少时可以调用restoreStrongReferences()方法恢复缓存对象的强引用。
void highMemory() {
myScopeReferences.restoreStrongReferences();
}
延迟注入(Lazy injections)
如果你想要对象延迟实例化,对于绑定T
,你可以创建一个Lazy<T>
,它会延迟到Lazy<T>
第一次调用get()
方法时才会实例化。如果T
是单例,那么对象依赖图内的所有注入将会拥有唯一一个Lazy<T>
实例,否则每个注入会有自己的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注入(Provider injections)
如果你想要拿到T
的多个注入实例,可以使用注入Provider<T>
而不是注入T
(虽然使用Factories, Builders等也可以实现),每次调用Provider<T>
的get()
方法都会调用一次绑定逻辑,如果绑定逻辑是@Inject构造器,那么每次都会创建新的实例,如果是@Provides方法就没法保证了。不过Provider<T>
也可能会使代码变得混乱,最好使用factory或Lazy<T>
或重新组织生命周期和代码结构来注入T
,当然Provider<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();
...
}
}
}
限定符(Qualifiers)
如果单一类型不足以描述一个依赖,可以使用限定符来描述。例如我们新增一个限定符注解@Named:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}
然后,就可以用这个限定符注解去注解字段或感兴趣的参数了:
class ExpensiveCoffeeMaker {
@Inject @Named("water") Heater waterHeater;
@Inject @Named("hot plate") Heater hotPlateHeater;
...
}
@Provides @Named("hot plate") static Heater provideHotPlateHeater() {
return new ElectricHeater(70);
}
@Provides @Named("water") static Heater provideWaterHeater() {
return new ElectricHeater(93);
}
可选的绑定(Optional bindings)
如果你想某个绑定在component的某些依赖不满足的情况下也能工作,可以给Module添加一个@BindsOptionalOf方法:
@BindsOptionalOf abstract CoffeeCozy optionalCozy();
这就意味着@Inject构造器和成员和@Provides方法可以依赖一个Optional<CoffeeCozy>
对象,如果component绑定了CoffeeCozy
那么Optional就是当前的,否则Optional就是缺省的。你可以注入一下几种类型:
* Optional
* Optional
绑定实例(Binding Instances)
如果你想在绑定component时注入参数,如app需要一个用户名参数,就可以给component的builder方法添加一个[@BindsInstance][BindsInstance]方法注解以使用户名字符串实例可以被注入到component中:
@Component(modules = AppModule.class)
interface AppComponent {
App app();
@Component.Builder
interface Builder {
@BindsInstance Builder userName(@UserName String userName);
AppComponent build();
}
}
public static void main(String[] args) {
if (args.length > 1) { exit(1); }
App app = DaggerAppComponent
.builder()
.userName(args[0])
.build()
.app();
app.run();
}
编译时验证(Compile-time Validation)
Dagger2注解处理器是严格模式的,如果所有的绑定无效或者不完整就会导致编译时错误。例如这个module被安装到component,但是忘了绑定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.
只需要在当前component的任一Module中新增一个Executor的@Provides方法即可修复这个错误。
编译时生成代码(Compile-time Code Generation)
Dagger的注解处理器可能会自动生成像CoffeeMaker_Factory.java或CoffeeMaker_MembersInjector.java等源文件。这些文件就是Dagger的实现细节,你不需要直接去使用它们(虽然它们有利于注入时的单步调试),而你代码中只需要使用以Dagger开头的component就可以了。
在Android平台上的使用
哲理(Philosophy)
虽然Android应用使用Java语言,但代码的书写原则和书写风格还是和普通Java应用不同,最典型就是要考虑移动端设备的性能,一些特性不建议在Android平台上使用。
为了能自动生成既地道又轻量的代码,Dagger依靠混淆器(ProGuard)去处理编译后的字节码。这就保证了当使用不同的工具链生成可高效运行的字节码时,Dagger在server和Android都可生成自然流畅的源码。而且,Dagger也会确保自动生成的源码可以完全兼容混淆优化(ProGuard optimizations)。
以上是关于Dagger2 User's Guide(翻译)的主要内容,如果未能解决你的问题,请参考以下文章
SQL Procedure User's Guide (Multiple Table)
Hyperledger Fabric CA User’s Guide——概述
Hyperledger Fabric CA User’s Guide——CA用户指南