依赖注入(二)—— Dagger的ComponentBuilder与ComponentFactory

1,278 阅读4分钟

Component Builder

在前面我们提到,如果Dagger想要使用或者支持注入由外界提供的对象时,需要通过手动创建Module,传入外界对象,然后再将module对象设置给Component。

@Module
class CommonModule(
    private val context: Context
) {
    @Provides
    fun context(): Context = context
    
    @Provides
    fun provideSharedPreferences(): SharedPreferences {
        return context.getSharedPreferences("SP_FILE", Context.MODE_PRIVATE)
    }
}

@Component(modules = [CommonModule::class])
interface AppComponent {
    fun inject(application: MyApplication)
}

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.builder()
            .commonModule(CommonModule(this))
            .build()
            .inject(this)
        ...
    }
}

在上面的例子中,DaggerAppComponent.builder()用于获取DaggerAppComponent的建造者实例,用于对DaggerAppComponent的实例进行一些配置。

当没有特殊声明时,Dagger会自动为Component生成对应的Builder,并且Dagger会自动检索该Component依赖的Module和Component,在Builder中为每一个Module和Component生成setter

public static Builder builder() {
  return new Builder();
}

public static final class Builder {
  private CommonModule commonModule;
  private BaseComponent baseComponent;

  private Builder() { }

  public Builder commonModule(CommonModule commonModule) {
    this.commonModule = Preconditions.checkNotNull(commonModule);
    return this;
  }

  public Builder baseComponent(BaseComponent baseComponent) {
    this.baseComponent = Preconditions.checkNotNull(baseComponent);
    return this;
  }

  public MyComponent build() {
    Preconditions.checkBuilderRequirement(commonModule, AModule.class);
    Preconditions.checkBuilderRequirement(baseComponent, BComponent.class);
    return new MyComponentImpl(commonModule, baseComponent);
  }
}

我们也可以对Component的Builder进行显式声明,以复用一些代码或者将依赖Module和Component的初始化逻辑收拢:

@Component(modules = [CommonModule::class])
interface AppComponent {
    fun inject(application: MyApplication)
    
    // Subcomponent需要使用@Subcomponent.Builder
    // 关于Subcomponent的更多介绍参见后文[Component之间依赖项的复用]。
    @Component.Builder
    abstract class Builder : BaseBuilder<AppComponent> {
        fun context(context: Context): Builder {
            return setCommonModule(CommonModule(context))
        }
        protected abstract fun setCommonModule(module: CommonModule): Builder
    }
}

DaggerAppComponent.builder()
    .context(this)
    .build()
    .inject(this)

需要注意的是,显式声明Builder时,我们必须为Component依赖没有默认构造函数的Module依赖定义setter,否则将编译失败;并且在使用Builder构造Component时,也必须调用这些setter,否则将会抛出运行时错误。

Component Factory

除了使用Builder来定制Component外,Dagger还提供了Component Factory方式来定制Component。

那么既然已经有Builder了,为何还要提供Factory呢?

既生Builder,何生Factory?

这是因为Builder方式在使用上有一些不方便,当我们要构造的Component有多个依赖的Module和Component时,Component依赖没有默认构造函数的Module依赖必须要手动调用setter,然后才能调用build函数生成Component实例。而我们在使用时很容易忘记某个setter的调用,这种错误又不会在编译期间被检测出来,仅在运行时抛出错误。

因此,Dagger提供了Factory,通过严格限制Factory中create方法的参数为Component依赖的Module和Component实例,将原本在运行时的检查提前到了编译时,从而提升了使用体验。

接下来让我们看看如何使用Component Factory。

如何使用

和Builder不一样,默认情况下Dagger并不会为Component生成相应的Factory实现,我们需要显式的为Component声明Factory:

@Component(
    modules = [CommonModule::class],
    dependencies = [BaseComponent::class]
)
interface AppComponent {
    fun inject(application: MyApplication)

    // Subcomponent需要使用@Subcomponent.Factory。
    @Component.Factory
    interface Factory {
        // Factory中只能定义一个方法,且该方法接受Component依赖的Module和Component,
        // 返回Component实例。
        fun create(
            baseComponent: BaseComponent, 
            commonModule: CommonModule
        ): MyComponent
    }
}

与Builder一样,我们也必须在create方法中将Component依赖没有默认构造函数的Module依赖作为参数传递,否则将会编译错误。

编译后Dagger就会自动为我们生成当前Component对应的Factory实现类:

public static MyComponent.Factory factory() {
  return new Factory();
 }

private static final class Factory implements MyComponent.Factory {
  @Override
  public MyComponent create(BaseComponent baseComponent, CommonModule commonModule) {
    Preconditions.checkNotNull(baseComponent);
    Preconditions.checkNotNull(commonModule);
    return new MyComponentImpl(commonModule, baseComponent);
  }
}

使用时和Builder类似,通过factory静态方法获取工厂实例,调用create方法即可:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.factory()
            .create(baseComponent, CommonModule(this))
            .inject(this)
        ...
    }
}

将外部对象添加到Dagger中的简便方法

让我们回到文章开始的例子中,为了向Dagger中提供Context,无论使用Builder还是Factory,我们都不得不为其创建一个CommonModule实例,然后设置给AppComponent,这个步骤多少还是有些繁琐,因此Dagger为我们提供了一个新的注解@BindsInstance用于直接将外部对象绑定到Dagger上。

@BindsInstance有两种使用方式:

在Builder中使用

@Component(modules = [PrefModule::class])
interface AppComponent {
    fun inject(application: MyApplication)

    @Component.Builder
    interface Builder {
        @BindsInstance
        fun context(context: Context): Builder
        // 有默认构造函数的Module可以省略setter定义
        // fun prefModule(module: PrefModule): Builder
        fun build(): MyComponent
    }
}

// module中不再需要context的Provides-method
@Module
class PrefModule {
    @Provides
    fun provideSharedPreferences(
        context: Context // context现在可以作为依赖项直接注入了
    ): SharedPreferences {
        return context.getSharedPreferences("SP_FILE", Context.MODE_PRIVATE)
    }
}

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.builder()
            .context(this)
            .build()
            .inject(this)
        ...
    }
}

在Factory中使用

@Component(modules = [PrefModule::class])
interface AppComponent {
    fun inject(application: MyApplication)

    @Component.Factory
    interface Factory {
        fun create(
            @BindsInstance context: Context
            // 同样的有默认构造函数的Module可以省略
            // , prefModule: PrefModule
        ): MyComponent
    }
}

// module中不再需要context的Provides-method
@Module
class PrefModule {
    @Provides
    fun provideSharedPreferences(
        context: Context // context现在可以作为依赖项直接注入了
    ): SharedPreferences {
        return context.getSharedPreferences("SP_FILE", Context.MODE_PRIVATE)
    }
}

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.factory()
            .create(this)
            .inject(this)
        ...
    }
}