依赖注入(四)—— Dagger的命名标记Named和限定符Qualifier

510 阅读2分钟

从一个问题开始

假如我们需要向项目中提供两种Gson实例:一个普通实例,一个带有驼峰-下划线命名转换的实例。

@Module
class GsonModule {
    @Singleton
    @Provides
    fun gsonNormal(): Gson {
        return GsonBuilder().create()
    }

    @Singleton
    @Provides
    fun gsonNaming(): Gson {
        return GsonBuilder()
            .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
            .create()
    }
}

这种情况下我们应该如何获取到想要的依赖项实例呢?

@Singleton
@Component(modules = [GsonModule::class])
interface CommonComponent {
    fun getGson(): Gson        // Dagger如何知道我们希望获取到
    fun getGsonNaming(): Gson  // GsonModule中的哪个实例呢?
}

class Class {
    // Dagger如何知道我们希望获取到GsonModule中的哪个实例呢?
    @Inject
    lateinit var gson: Gson 
    @Inject
    lateinit var gsonNaming: Gson
}

如果直接像上面这样写,我们在编译时就会收到一个错误:

错误: [Dagger/DuplicateBindings] com.google.Gson is bound multiple times:

因为对于Gson类型的依赖项,存在两个Provides方法,Dagger无法确定使用哪一个,这种情况被称之为依赖迷失。那么该如何解决这个问题呢?Dagger提供了两种方式来解决此类问题。

Named

@Named注解会为依赖提供方法设置一个标记名称,Dagger在编译时会读取该标记名称,当注入字段或者依赖获取接口也使用同一标记名称时,Dagger就会将该依赖提供方法和对应的依赖注入位置相绑定。

使用方式如下:

  1. 先使用@Named标记Module中生成依赖实例的Provides方法。

    @Module
    class GsonModule {
        @Singleton
        @Provides
        @Named("gson-normal")
        fun gsonNormal(): Gson { ... }
    
        @Singleton
        @Provides
        @Named("gson-naming")
        fun gsonNaming(): Gson { ... }
    }
    
    ⚠️ `@Named`注解和后面自定义的限定符只能用来修饰`Provides`方法。
  2. 使用@Named标记依赖获取接口或依赖注入位置,注意@Named中定义的标记名称需要和修饰Provides方法的名称一样。

    @Singleton
    @Component(modules = [GsonModule::class])
    interface CommonComponent {
        @Named("gson-normal")
        fun getGson(): Gson
        @Named("gson-naming")
        fun getGsonNaming(): Gson
    }
    
    class Class {
        @Named("gson-normal")
        @Inject
        lateinit var gson: Gson
        @Named("gson-naming")
        @Inject
        lateinit var gsonNaming: Gson
    }
    

Qualifier

除了使用Dagger预置的@Named注解外,我们还可以使用@Qualifier元注解定义自己的限定符:

@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GsonType(val value: String)

// 或者

@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GsonTypeNaming {
}

@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class GsonTypeNormal {
}

自定义限定符的使用方式和@Named一致,只要保证同一类型依赖项的不同Provides方法被不同的限定符修饰即可(限定符的字段取值不同,限定符也不同)。另外自定义限定符的字段可以随意定义,只要保证使用时赋值给字段的参数不一样即可。

@GsonTypeNormal
@Provides fun gsonNormal(): Gson { ... }
@GsonTypeNaming
@Provides fun gsonNaming(): Gson { ... }

@GsonType("Normal")
@Provides fun gsonNormal(): Gson { ... }
@GsonType("Naming")
@Provides fun gsonNaming(): Gson { ... }