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
,它们之间会有相同的依赖,那么该如何处理它们之间的关系呢?
我们先来看一个简单的例子:
假如有三个类:A
、B
和C
, A
和B
都各自持有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)
}
如果要让A
、B
持有的实例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)有时依赖实例需要共享,例如上面场景中,A
和C
都持有C
的实例,并且根据Module
里的实现,会存在谁先创建C
实例的问题。
(2)Scope 作用域容易失效,例如 CModule
的provideCar()
使用 @Singleton
作用域,AComponent
和BComponent
也要用 Singleton
标注,但它们都会持有一个C
实例。
假如A
先创建了C
那么,BComponent
需要依赖 AComponent
提供的 C
实例,这就是 Component
组织关系中的一种。
Component 的组织关系
Component 管理着依赖实例,根据依赖实例之间的关系就能确定 Component 的关系。这些关系可以用object graph
描述,我称之为依赖关系图。在 Dagger 2 中 Component 的组织关系分为两种:
- 依赖关系,一个 Component 依赖其他 Compoent
公开
的依赖实例,用 Component 中的dependencies
声明。 - 继承关系,一个 Component 继承(也可以叫扩展)某 Component 提供更多的依赖,SubComponent 就是继承关系的体现。
所以前文中AComponent
和BComponent
是依赖关系。
依赖关系
具体的实现代码:
@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)
依赖关系就跟生活中的朋友关系相当,注意事项如下:
- 被依赖的 Component 需要把暴露的依赖实例用显式的接口声明。
- 依赖关系中的 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。