Dagger 2 完全解析(三),Component与SubComponent

Dagger 2 Component 与 SubComponent


[Dagger 2 完全解析(一),基本使用与原理
Dagger 2 完全解析(二), 进阶使用
Dagger 2 完全解析(三), Component 与 SubComponent
Dagger 2 完全解析(四),在Android中的使用

本系列文章是基于 Google Dagger 2.23.2 版本, Kotlin 1.3.21版本


理解前面两篇文章后,可以使用 Dagger 2框架完成一个对象的依赖注入。但是在实战中会存在多个需要注入依赖的对象,也就是说会有多个 Component,它们之间会有相同的依赖,那么该如何处理它们之间的关系呢?

我们先来看一个简单的例子:

假如有三个类:ABC, AB都各自持有C的实例,并且通过Dagger2进行注入

class A {
    @Inject
    lateinit var c: C
}

class B {
    @Inject
    lateinit var c: C
}

class C @Inject constructor()

Component:

@Component
interface AComponent {
    fun injectA(a: A)
}

@Component
interface BComponent {
    fun injectA(b: B)
}

如果要让AB持有的实例C相同的话,我们该如何处理?

很多人第一时间会想到下面这种设计:

@Singleton
@Component(modules = [CModule::class])
interface AComponent {
    fun injectA(a: A)
}

@Singleton
@Component(modules = [CModule::class])
interface BComponent {
    fun injectA(b: B)
}

@Module
class CModule {

    companion object {
        private var c: C? = null
    }

    @Singleton
    @Provides
    fun provideC(): C {
        if (c == null) {
            c = C()
        }
        return c!!
    }
}

把 Component 需要的依赖都在modules属性中声明,但是这样有两个问题:

(1)有时依赖实例需要共享,例如上面场景中,AC都持有C的实例,并且根据Module里的实现,会存在谁先创建C实例的问题。

(2)Scope 作用域容易失效,例如 CModuleprovideCar()使用 @Singleton 作用域,AComponentBComponent也要用 Singleton 标注,但它们都会持有一个C实例。

假如A先创建了C那么,BComponent需要依赖 AComponent提供的 C实例,这就是 Component 组织关系中的一种。

Component 的组织关系

Component 管理着依赖实例,根据依赖实例之间的关系就能确定 Component 的关系。这些关系可以用object graph描述,我称之为依赖关系图。在 Dagger 2 中 Component 的组织关系分为两种:

  • 依赖关系,一个 Component 依赖其他 Compoent公开的依赖实例,用 Component 中的dependencies声明。
  • 继承关系,一个 Component 继承(也可以叫扩展)某 Component 提供更多的依赖,SubComponent 就是继承关系的体现。

所以前文中AComponentBComponent是依赖关系。

依赖关系

具体的实现代码:

@Component(modules = [CModule::class])
interface AComponent {
    fun injectA(a: A)

    fun c(): C
}

@Component(dependencies = [AComponent::class])
interface BComponent {
    fun injectA(b: B)
}

@Module
class CModule {
    @Provides
    fun provideC(): C = C()
}

因为 BComponent和 AComponent是依赖关系,如果AComponent声明了作用域的话,那么BComponent也必须声明(反之可以),而且它们的 Scope 不能相同,并且两个都有作用域的情况下 @Singleton 修饰的 Component (BComponent)不能依赖其他的 Component。

public final class DaggerBComponent implements BComponent {
  private final AComponent aComponent;

  private DaggerBComponent(AComponent aComponentParam) {
    this.aComponent = aComponentParam;
  }
 ....

  @Override
  public void injectA(B b) {
    injectB(b);}

  private B injectB(B instance) {
      // 注入时,使用了aComponent.c()
    B_MembersInjector.injectC(instance, Preconditions.checkNotNull(aComponent.c(), "Cannot return null from a non-@Nullable component method"));
    return instance;
  }
...
}

编译时生成的代码 DaggerBComponent 中会调用aComponent.c(),如果 AComponent 没有向外提供C 实例的接口的话,DaggerBComponent 就会注入失败。

依赖注入:

val aComponent = DaggerAComponent.builder().build()
DaggerBComponent.builder().aComponent(aComponent).build().inject(b)

依赖关系就跟生活中的朋友关系相当,注意事项如下:

  1. 被依赖的 Component 需要把暴露的依赖实例用显式的接口声明。
  2. 依赖关系中的 Component 的 Scope 不能相同,因为它们的生命周期不同。

继承关系

继承关系跟面向对象中的继承的概念有点像,SubComponent称为子 Component,类似于平常说的子类。下面先看看下面这个场景:

class Parent {
    @Inject
    lateinit var car: Car
}

class Child {
    @Inject
    lateinit var car: Car
    @Inject
    lateinit var bike: Bike
}

class Car @Inject constructor()
class Bike @Inject constructor()

Child 可以开Parent的车 car,也可以骑自己的自行车 bike。依赖关系图:

在这里插入图片描述

上图中 ChildComponent 在 ParentComponent之中,ChildComponent子承父业,可以访问 ParentComponent 的依赖,而 ParentComponent只知道 ChildComponent 是它的子类,可以访问 SubComponent.Builder,却无法访问 SubComponent 中的依赖。

@Component(modules = [CarModule::class])
interface ParentComponent {
    fun inject(parent: Parent)
}

@Subcomponent(modules = [BikeModule::class])
interface ChildComponent {
    fun inject(child: Child)

    // SubComponent 必须显式地声明 Subcomponent.Builder,parentComponent 需要用 Builder 来创建 ChildComponent
    @Subcomponent.Builder
    interface Builder {
        fun build(): ChildComponent
    }
}

@SubComponent的写法与@Component一样,只能标注接口或抽象类,与依赖关系一样,SubComponent 与 parent Component 的 Scope 不能相同,只是 SubComponent 表明它是继承扩展某 Component 的。怎么表明一个 SubComponent 是属于哪个 parent Component 的呢?只需要在 parent Component 依赖的 Module 中的subcomponents加上 SubComponent 的 class,然后就可以在 parent Component 中请求 SubComponent.Builder。

@Component(modules = [CarModule::class])
interface ParentComponent {
    ...
	// 用来创建childComponent
    fun childComponent(): ChildComponent.Builder
}

@Subcomponent(modules = [BikeModule::class])
interface ChildComponent {
	...
    // SubComponent 必须显式地声明 Subcomponent.Builder,parentComponent 需要用 Builder 来创建 ChildComponent
    @Subcomponent.Builder
    interface Builder {
        fun build(): ChildComponent
    }
}
// 在CarModule上添加subComponents
@Module(subcomponents = [ChildComponent::class])
class CarModule {
    @Provides
    fun provideCar() = Car()
}

@Module
class BikeModule {
    @Provides
    fun provideBike() = Bike()
}

上面的代码经过make project后,dagger2生成的代码:

public final class DaggerParentComponent implements ParentComponent {
  ...
  @Override
  public ChildComponent.Builder childComponent() {
    return new ChildComponentBuilder();}
  ...
  private final class ChildComponentBuilder implements ChildComponent.Builder {
    @Override
    public ChildComponent build() {
      return new ChildComponentImpl(new BikeModule());
    }
  }

  private final class ChildComponentImpl implements ChildComponent {
    private final BikeModule bikeModule;

    private ChildComponentImpl(BikeModule bikeModuleParam) {
      this.bikeModule = bikeModuleParam;
    }
   ...
    private Child injectChild(Child instance) {
      // 注入car时,调用的parentComponent的carModule提供
      Child_MembersInjector.injectCar(instance, CarModule_ProvideCarFactory.provideCar(DaggerParentComponent.this.carModule));
      Child_MembersInjector.injectBike(instance, BikeModule_ProvideBikeFactory.provideBike(bikeModule));
      return instance;
    }
  }
}

SubComponent 编译时不会生成 DaggerChildComponent,需要通过 parentComponent 的获取 SubComponent.Builder 方法获取 ChildComponent 实例。

val parentComponent = DaggerParentComponent.builder().build()
parentComponent.childComponent().build().inject(child)

继承关系和依赖关系最大的区别就是:继承关系中不用显式地提供依赖实例的接口,SubComponent 继承 parent Component 的所有依赖。

依赖关系 vs 继承关系

相同点:

  • 两者都能复用其他 Component 的依赖
  • 有依赖关系和继承关系的 Component 不能有相同的 Scope

区别:

  • 依赖关系中被依赖的 Component 必须显式地提供公开依赖实例的接口,而 SubComponent 默认继承 parent Component 的依赖。
  • 依赖关系会生成两个独立的 DaggerXXComponent 类,而 SubComponent 不会生成 独立的 DaggerXXComponent 类。

在 Android 开发中,Activity 是 App 运行中组件,Fragment 又是 Activity 一部分,这种组件化思想适合继承关系,所以在 Android 中一般使用 SubComponent。

SubComponent 的其他问题

抽象工厂方法定义继承关系

除了使用 Module 的subcomponents属性定义继承关系,还可以在 parent Component 中声明返回 SubComponent 的抽象工厂方法来定义:

@Component(modules = [CarModule::class])
interface ParentComponent {
    ...
	// 用来创建childComponent
     这个抽象工厂方法表明 ChildComponent 继承 ParentComponent
    fun childComponent(): ChildComponent
}

@Subcomponent(modules = [BikeModule::class])
interface ChildComponent {
	...
}

@Module
class CarModule {
    @Provides
    fun provideCar() = Car()
}

@Module
class BikeModule {
    @Provides
    fun provideBike() = Bike()
}

这种定义方式不能很明显地表明继承关系,一般推荐使用 Module 的subcomponents属性定义。

重复的 Module

当相同的 Module 注入到 parent Component 和它的 SubComponent 中时,则每个 Component 都将自动使用这个 Module 的同一实例。也就是如果在 SubComponent.Builder 中调用相同的 Module 或者在返回 SubComponent 的抽象工厂方法中以重复 Module 作为参数时,会出现错误。(前者在编译时不能检测出,是运行时错误)

@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
  ComponentTwo componentTwo(RepeatedModule repeatedModule); // 编译时报错
  ComponentThree.Builder componentThreeBuilder();
}

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentTwo { ... }

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentThree {
  @Subcomponent.Builder
  interface Builder {
    Builder repeatedModule(RepeatedModule repeatedModule);
    ComponentThree build();
  }
}

DaggerComponentOne.create().componentThreeBuilder()
    .repeatedModule(new RepeatedModule()) // 运行时报错 UnsupportedOperationException!
    .build();

总结

Component 之间共用相同依赖,可以有两种组织关系:依赖关系与继承关系。至于如何选择试具体情况而定,但在 Android 开发中,一般使用继承关系,以 AppComponent 作为 root Component,AppComponent 一般还会使用 @Singleton 作用域,而 ActivityComponent 为 SubComponent。

posted @ 2019-06-26 21:46  jxiaow  阅读(297)  评论(0编辑  收藏  举报