有时候我们需要Dagger为我们提供某个类型的两个不同的实例,可以使用@Named限定符来实现。比如本例中我们需要两只狗,先用@Module注解定义了一个模块,其中使用@Providers注解告诉Dagger可以提供Dog类实例,并且还使用了@Named(xxx)注解区分,否则编译会报错,Dagger不允许多处提供同一类型的实例,但使用命名后,会生成不同的工厂类和提供实例方法。
@Module
class AnimalsModule {
@Provides
@Singleton
@Named("SingleDog")
fun providerSingleDog(): Dog {
return Dog()
}
@Provides
@Singleton
@Named("LickDog")
fun providerLickDog(): Dog {
return Dog()
}
}
在DemoZoo类中定义了需要注入依赖的属性
class DemoZoo {
@Inject
@Named("SingleDog")
lateinit var singleDog: Dog
@Inject
@Named("LickDog")
lateinit var lickDog: Dog
}
看着没啥问题,编译,哦豁,报错
[Dagger/MissingBinding] com.example.demo.Dog cannot be provided without an @Inject constructor or an @Provides-annotated method.
public abstract interface DemoDaggerComponent {
^
com.example.demo.Dog is injected at
com.example.demo.DemoZoo.dog
大致就是 DemoZoo类需要Dagger提供Dog实例,但是没有找到可以提供的地方。
正常应该是用@Named("LickDog")注解的lickDog属性,所需要的依赖对象是由@Provides,@Named("LickDog")注解提供的,但错误信息却是找不到。
百度和Google搜索了下,都是提到@Named(XXX)要改成@field:Named(XXX), 然后修改了一下,的确编译没问题了,但是这样做又是为什么呢,查看了Kotlin的文档,有提到@field注解的用法。
在Kotlin中, var xxx的属性,生成的Java字节码会有多个地方,比如getXXX()、setXXX()、成员变量xxx, kotlin编译器就需要借助注解使用处目标来精确指定如何生成该注解,比如@field:就是说后面跟的注解要用在属性变量上。但是,问题又来了,那为啥@Inject注解不需要?
前面截图下面还提到
如果不指定使用处目标,则根据正在使用的注解的 @Target 注解来选择目标 。如果有多个适用的目标,则使用以下列表中的第一个适用目标:
- param
- property
- field
点击@Inject看到定义是这样的,申明了@Target, 可以用在方法、构造函数和属性元素上
@Target({ METHOD, CONSTRUCTOR, FIELD })
@Retention(RUNTIME)
@Documented
public @interface Inject {}
所以@Inject lateinit var wangCai: Dog中
@Inject虽然没有用@filed:指定,但编译器会使用@Target注解来选择目标,这里应该是有三个适用目标,分别是get、set和field,根据第一适用目标规则就是filed,所以在Java字节码中是在属性上进行注解。
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
/** The name. */
String value() default "";
}
@Named没有申明@Target,编译器不知道如何处理,会忽略该注解,生成的Java字节码没有该注解。
查看kotlin 字节码反编译后的java代码存在@Inject,但没有@Named注解。
public final class DemoZoo {
@Inject
public Dog singleDog;
@Inject
public Dog lickDog;
@Inject
public DogCatBug catBug;
@Inject
public Cat cat;
}
修改代码,作为对比,一个改为@field:Named,一个保留原样
//DemoZoo.kt
class DemoZoo {
@Inject
@Named("SingleDog")
lateinit var singleDog: Dog
@Inject
@field:Named("LickDog")
lateinit var lickDog: Dog
}
//kotlin bytecode decompile
public final class DemoZoo {
@Inject
public Dog singleDog;
@Inject
@Named("LickDog")
public Dog lickDog;
@Inject
public DogCatBug catBug;
@Inject
public Cat cat;
}
使用@field:Named语法的注解出现在了java字节码里,所以能被Dagger处理。
有了以上的了解,我尝试自定义了一个CusNamed注解,就是在Named类的基础上增加了@Target,然后把刚才使用@Named注解的地方替换成@CusNamed,重新编译也没有问题。
@Target({ METHOD, FIELD })
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface CusNamed {
/** The name. */
String value() default "";
}
问题的根本原因,实际上就是Named注解没有定义Target,然后呢Kotlin元素生成Java元素时对注解的处理方式导致的,这里还是建议使用@field:Named语法,毕竟很多项目都这样写。