依赖注入(七)—— Dagger的MultiBindings

1,747 阅读3分钟

Dagger通过多元绑定可以将多个依赖项放入到一个集合中,即使他们由不同的Module提供。集合的组装由Dagger自动完成,所以在往代码中注入时,不需要直接依赖于每个对象。使用多元绑定可以实现一个插件架构:由几个Module分别提供各自的服务,核心类就可以直接使用这些服务而无需手动获取。

Dagger提供了两种容器实现的多元绑定,SetMap

Set

使用@IntoSet@ElementIntoSet注解为Set提供元素:

@Module
class ModuleA {
    // 提供一个元素
    @Provides @IntoSet
    fun provideString(): String = "ABC"
}
// 多个模块可以向同一个Set中提供元素
@Module
class ModuleB {
    // 提供多个元素
    @Provides @ElementIntoSet
    fun provideStrings(): Set<String> = setOf("DEF", "GHI")
}

多元绑定提供的Set就和其它依赖项一样,可以在依赖图中直接使用,或者通过Component向外提供,同时ScopeQuailfierProvider包装这些同样对Set有效:

@Component(modules = [ModuleA::class, ModuleB::class])
interface MyComponent {
    fun strings(): Lazy<Set<String>>
}

class Bar @Inject constructor(strings: Set<String>) {
}

需要注意的是,默认情况下Dagger会根据元素的类型将元素设置到对应的Set中,如果需要将相同类型的元素分别设置到不同的集合中时,则需要使用Qualifier对元素的provides-method和要注入的Set进行限定。

@Module
class ModuleC {
    @Named("A")
    @Provides @IntoSet
    fun provideStringA(): String = "AAA"
    @Named("B")
    @Provides @IntoSet
    fun provideStringB(): String = "AAA"
}

@Inject
@Named("A")
lateinit var strsA: Set<String>

@Inject
@Named("B")
lateinit var strsB: Set<String>

Map

给Map提供元素时,除了要使用@IntoMap注解标记provides-method外,还需要为提供的元素指明Key值。

预置的Key类型

Dagger默认支持一些基本的类型作为key值:ClassIntLongString,这些类型的Key都有与之对应的注解:@ClassKey@IntKey@LongKey@StringKey,使用时直接用这些注解去标记提供的元素即可:

@Module
class ModuleD {
    @Provides
    @IntoMap @StringKey("foo")
    fun provideFooValue(): Long = 1000L

    @Provides
    @IntoMap @ClassKey(Thing::class)
    fun provideTingValue(): String = "value of Thing"
}

自定义Key类型

使用@MapKey元注解我们可以自定义Key类型注解,自定义的Key类型注解需要通过唯一的value成员指定key的具体值。

enum class Type {
    A, B
}

@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER,
    AnnotationTarget.FIELD,
    AnnotationTarget.ANNOTATION_CLASS,
    AnnotationTarget.CLASS
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class TypeKey(val value: Type)

@Provides
@IntoMap @TypeKey(Type.A)
fun provideTypeAValue(): String = "value of Type A"

如果需要将多个值作为键的话,则需要为@MapKey设置unwrapeValue = true,这样就可以将定义的注解中的所有成员都作为键。

使用

同样的,我们也可以像使用其它依赖项那样使用Map。默认情况下,Dagger也是根据Key值类型和元素类型来将元素设置到不同的集合中,如果需要对拥有相同键类型和值类型的元素进行区分的话,则需要使用Qualifier对元素和要注入的Map进行限定。

@MultiBinds绑定空容器

假如我们使用了@IntoMap@IntoSet实现了多元绑定,但是因为一些原因,容器里暂时没有元素被注入(比如我提供了一个SDK,依赖使用它的开发者为这些容器里提供依赖项),此时我们可能需要注入一个空容器(开发者没有编写@IntoMap@IntoSet会导致容器的注入位置报错)。

此时我们可以使用@MultiBinds@MultiBinds告诉Dagger,在遇到某种容器的注入点时,如果找不到对应的@IntoMap@IntoSet绑定,就注入一个空容器。

@Module
interface DefaultModule {
    @MultiBinds
    fun bindEmptyIds(): Set<String>
}
@Component()
interface AppComponent {
    fun inject(client: Client)
}
class Client {
    @Inject lateinit var ids: Set<String>
}

当然你也可以提供一个返回空容器的provides-method,然后用@IntoMap@IntoSet标记它,但是这种方式相比@MultiBinds会创建provides-method的工厂类,造成不必要的开销。