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——概述

KEILUser's Guide

Hyperledger Fabric CA User’s Guide——CA用户指南

Java Developer's Guide to SSL Certificates

Beginner's Guide to Python-新手指导