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<CoffeeCozy>
  • Optional<Provider<CoffeeCozy>>
  • Optional<Lazy<CoffeeCozy>>
  • Optional<Provider<Lazy<CoffeeCozy>>>

绑定实例(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)。

专门为Android准备的相关类(dagger.android)

Android应用使用Dagger最主要的困难就是一些Framework类(如Activity、Fragment)是由操作系统实例化的,而Dagger更好工作的前提是它可以构建所有的注入对象。所以,你只能(不得不)在生命周期方法中进行成员变量注入,这就意味着一些类可能会写成这个样子:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}

而这样的代码有几个问题:

  1. 很多类都复制粘贴这样的代码会让之后的重构变得很困难,如果越来越多开发者复制粘贴这样的代码块,就很少有人知道这段代码究竟干了什么了。
  2. 更重要的是,请求注入的类型(如这里的FrombulationActivity)需要知道它的注入器是什么。即使这些是通过接口而不是具体类型完成的,但它打破了依赖注入的核心原则:一个类不应该知道任何和注入实现细节相关的东西。

dagger.android包下面的类提供了简化这一模式的手段。

注入Activity对象(Injecting Activity objects)

  1. AndroidInjectionModule安装到你的application component,以保证这些基本类型所需的所有绑定是可用的。
  2. 写一个实现了[AndroidInjector<YourActivity>][AndroidInjector]的Subcomponent,Subcomponent的Builder继承[AndroidInjector.Builder<YourActivity>][AndroidInjector.Builder]即可。

    @Subcomponent(modules = ...)
    public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
    @Subcomponent.Builder
    public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
    }
  3. 定义好subcomponent之后,把它添加到你的component层次结构中(定义一个绑定了subcomponent builder的Module并把它添加到负责Application注入的那个component中):

    @Module(subcomponents = YourActivitySubcomponent.class)
    abstract class YourActivityModule {
    @Binds
    @IntoMap
    @ActivityKey(YourActivity.class)
    abstract AndroidInjector.Factory<? extends Activity>
       bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
    }
    
    @Component(modules = {..., YourActivityModule.class})
    interface YourApplicationComponent {}
  4. 接下来,让你的Application实现HasDispatchingActivityInjector,注入一个DispatchingAndroidInjector<Activity>activityInjector()返回:

    public class YourApplication extends Application implements HasDispatchingActivityInjector {
    @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
    
    @Override
    public void onCreate() {
     super.onCreate();
     DaggerYourApplicationComponent.create()
         .inject(this);
    }
    
    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
     return dispatchingActivityInjector;
    }
    }
  5. 最后,在你Activity的onCreate()方法调用super.onCreate(savedInstanceState);之前调用AndroidInjection.inject(this);就可以了:

    public class YourActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
     AndroidInjection.inject(this);
     super.onCreate(savedInstanceState);
    }
    }

经过上面这5步,你就可以非常方便地注入任何Activity对象了。但是它是怎么实现的呢?
AndroidInjection.inject()会从你的Application中拿到一个DispatchingAndroidInjector<Activity>并通过inject(Activity)把Activity传了过去。这个DispatchingAndroidInjector会查找一个你的Activity类的AndroidInjector.Factory(YourActivitySubcomponent.Builder),构建AndroidInjector (YourActivitySubcomponent),然后把你的Activity传给inject(YourActivity)

注入Fragment对象(Injecting Fragment objects)

注入一个Fragment就像注入一个Activity一样简单。用同样的方式创建一个subcomponent,然后把Activity类型参数换成Fragment就行了(@ActivityKey换成@FragmentKeyHasDispatchingActivityInjector换成HasDispatchingFragmentInjector)。
不像Activity需要在onCreate()方法中注入,Fragment要在onAttach()方法中注入。
不像Activity那样定义Module,你可以选择在何处为Fragment安装Module。你可以让你的Fragment component成为其它Fragment component或Activity component,甚至Application component的subcomponent,这完全取决于你的Fragment需要哪些绑定。决定好component的位置后,完成相应类型的HasDispatchingFragmentInjector实现。例如,如果你的Fragment需要来着YourActivitySubcomponent的绑定,你的代码就要像下面这样:

public class YourActivity extends Activity
    implements HasDispatchingFragmentInjector {
  @Inject DispatchingFragmentInjector<Fragment> fragmentInjector;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    // ...
  }

  @Override
  public DispatchingAndroidInjector<Activity> activityInjector() {
    return fragmentInjector;
  }
}

public class YourFragment extends Fragment {
  @Inject SomeDependency someDep;

  @Override
  public void onAttach(Activity activity) {
    AndroidInjection.inject(this);
    super.onAttach(activity);
    // ...
  }
}

@Subcomponent(modules = ...)
public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourFragment> {}
}

@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
  @Binds
  @IntoMap
  @FragmentKey(YourFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment>
      bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
}

@Subcomponent(modules = { YourFragmentModule.class, ... }
public interface YourActivityOrYourApplicationComponent { ... }

基本的Framework类型(Base Framework Types)

由于DispatchingAndroidInjector会在运行时查找合适的AndroidInjector,基类需要像调用AndroidInjection.inject()一样实现HasDispatchingActivityInjector/HasDispatchingFragmentInjector,子类需要做的就是绑定一个对应的@Subcomponent。如果你的类层次不是很复杂的话Dagger会提供一些基类(如[DaggerActivity]和[DaggerFragment])。同时Dagger也会提供[DaggerApplication],你需要做的就是继承它并覆写applicationInjector()方法以返回应该注入Application的component。
注意:[DaggerBroadcastReceiver]只适用于在AndroidManifest.xml文件中静态注册的BroadcastReceiver,如果你在代码中手动创建注册BroadcastReceiver,最好采用构造器注入。

支持库(Support libraries)

像使用Android support library一样,dagger.android.support包下面也会提供相应的类。但要注意,当support Fragment用户需要绑定AndroidInjector.Factory<? extends android.support.v4.app.Fragment>时,AppCompat用户应该继续实现AndroidInjector.Factory<? extends Activity>而不是<? extends AppCompatActivity><? extends FragmentActivity>

何时注入(When to inject)

要尽可能的采用构造器注入,因为javac将确保被set之前没有字段被引用,这有利于避免空指针异常。但如果你一定要注入成员变量,最好尽早进行注入(越早越好)。也正是因为这样,DaggerActivity才要在onCreate()方法中立刻调用AndroidInjection.inject()再调用super.onCreate()DaggerFragmentonAttach()也是一样,也是为了防止Fragment重新attach产生的矛盾。


references:

发布了48 篇原创文章 · 获赞 168 · 访问量 56万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览