美文网首页
基于Dagger2.38.1版本全面理解注解-前言和部分注解

基于Dagger2.38.1版本全面理解注解-前言和部分注解

作者: 我爱田Hebe | 来源:发表于2022-08-23 10:09 被阅读0次

前言

这里主要针对@MapKey、@Scope、@Qualifier注解还有@IntoSet,@IntoMap和@ElementsIntoSet的理解,因为这几个注解是贯穿始终的,和其他注解或多或少存在关联,如果不提前讲解,后面关联上无法更好的理解。

@MapKey、@Scope、@Qualifier三个注解仅仅可用于修饰注解。MapKey是Dagger2自定义的,Scope和Qualifier是javax.inject下的注解。

这里包括后面的章节中注解的先后顺序都是经过我精挑细选的,务必保证后面说到的注解关联的注解先被说到。

那个啥,为什么还是那么...咳咳,如同作者的发型那么柔顺!!!

@Scope

我们直译过来表示范围,这个注解是用于修饰注解的注解。

@Scope修饰的注解具体使用规则

  1. 节点使用@Scope修饰的注解最多只允许出现一个;

  2. 使用@Module或@ProducerModule修饰的module节点不允许使用Scope修饰的注解修饰;

  3. 绑定方法bindingMethod节点(module节点中的方法,使用@Provides 、@Produces 、@Binds 、@Multibinds、@BindsOptionalOf修饰)。其中@Provides和@Binds修饰的方法允许同时使用@Scope修饰的注解修饰,@Produces、@Multibinds和@BindsOptionalOf修饰的方法不允许同时使用@Scope修饰的注解修饰;

  4. @Inject或@AssistedInject修饰的构造函数不允许使用同时使用@Scope修饰的注解修饰;并且@AssistedInject修饰的构造函数所在的父级节点不允许使用使用@Scope修饰的注解修饰,但是使用@Inject修饰的构造函数所在父级节点允许使用@Scope修饰的注解修饰;

  5. @BindsInstance修饰的方法或方法参数不允许同时使用@Scope修饰;

Scope表示一个作用域,表示当前Scope修饰的注解修饰的节点所属的currentcomponent节点及其往下的subcomponent节点都可以使用该节点,我们以@Singleton为例:

Module节点,里面包含Subcomponent1,Subcomponent2,Subcomponent3几个subcomponent节点,Component节点中关联Module节点,Component节点的入口方法inject的作用是实例化MembersInjector类:

@Module(subcomponents = Subcomponent1.class,Subcomponent2.class,Subcomponent3.class)
public class Module{
    @Provides
    @Singleton
    public A bindingMethod(B b){
        return new A(b);
    }
}

@Component(modules = Module.class)
public calss Component{
    inject(MembersInjector m);

    public SubcomponentX getSubcomponentX();//这个也是一个Subcomponent节点
}


public class MembersInjector{
    
    @Inject
    public C C;
}


@Singleton
public class C {
    

    @Inject
    public C(){}
} 

已上面的实例解说如下:

  1. 当前@Singleton和@Provides修饰的bindingMethod方法返回的A的实例化既可以在当前Component作用域中使用,也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX(当然了,仅限于Subcompoonent中的实例化需要的A实例化对象的情况下)使用;

  2. 当前@Singleton修饰的C类(因为MembersInjector类是待实例化的对象,所以对象中@Inject修饰的变量类型(或普通方法返回类型))实例化既可以在当前Component作用域中使用也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX中使用;

  3. 那如果在Subcomponent1中定义了@Singleton修饰的节点,那么该节点的实例对象是不能再父级fathercomponent中使用的。

为什么有好几种@Scope修饰的注解,有什么不同?

Dagger当前2.38.1版本中源码中有几个Scope修饰的注解,@Reusable、@PerGeneratedFile、@PerComponentImplementation、@ProductionScope;还有javax.inject包下有一个@Singleton;

  1. @Reusable:@Reusable和@Binds同时修饰一个module节点中的bindingMethod方法,那么当前bindingMethod方法参数类型A节点如果没有使用@Scope修饰的注解,那么直接使用SingleCheck;如果A节点使用了@Scope修饰的注解,那么使用DoubleCheck;
  • SingleCheck和DoubleCheck的区别,SingleCheck单例获取A对象;DoubleCheck不仅仅获取A对象,并且校验当前A对象的实例化始终都是同一个实例;

     @Binds
         @Reusable
             // to avoid parsing options more than once
         CompilerOptions bindCompilerOptions(
                 ProcessingEnvironmentCompilerOptions processingEnvironmentCompilerOptions
         ); 
    
  • 如上代码案例,ProcessingEnvironmentCompilerOptions如果没有使用@Scope修饰的注解修饰,那么是SingleCheck单例,否则是DoubleCheck双重校验(单例 + 校验必须同一实例),其实对我们来说好像并没有多大影响;

  1. @Reusable其他情况和@Singleton用法都是在作用域中DoubleCheck双重校验;

  2. @PerGeneratedFile表示将当前修饰的节点及其往下关联的节点和当前所属的currentcomponent生成的代码写在同一个文件中,没有实际意义;

  3. @PerComponentImplementation:Dagger内部处理代码,相当于一个A类,但是这个A可能包含自己,那么我们将另外开辟一个用于生成子类A的实例,需要用到@PerComponentImplementation;

  4. @ProductionScope:如果使用了@ProductionComponent或@ProductionSubcomponent修饰的component节点,那么Dagger为我们生成一个@Module修饰的module节点,该module节点中有monitor方法:

     @Provides
     @ProductionScope
     static ProductionComponentMonitor monitor(Provider<component节点类型> component,Provider<Set<ProductionComponentMonitor.Factory>> factories){
         return Monitors.createMonitorForComponent(component, factories);
     } 
    
    • ProductionScope实际作用还是当前component的作用域,这里使用monitor方法的目的在于整个@ProductionComponent或@ProductionSubcomponent修饰的component节点的监控。

@Qualifier

直译过来是预选的意思。@Qualifier修饰的注解使用规则如下

  1. 节点上只允许出现一个@Qualifier修饰注解的注解;

  2. componentMethod方法只有在返回类型是subcomponent节点或subcomponent.creator节点情况下才允许使用Qualifier修饰的注解修饰;

  3. bindingMethod方法可以被Qualifier修饰的注解修饰;

  4. 被Inject或AssistedInject修饰的构造方法不允许使用Qualifier修饰的注解修饰,但是构造函数的参数可以;

  5. @Assisted修饰的参数节点不能被Qualifier注解修饰的注解修饰;

  6. MembersInjector的成员注入T类型不允许使用Qualifier修饰的注解修饰;

  7. 一个节点允许使用Qualifier修饰,那么当前节点类型(如果是方法,则表示方法返回类型)的构造函数允许使用AssistedInject修饰。

@Qualifier修饰的注解主要是为了实例化过程中成功匹配到正确的参数,我们以案例说话:

@Retention(RUNTIME)
@Qualifier
public @interface ProcessingOptions {}

public final class ExternalBindingGraphPlugins {

        @Inject
ExternalBindingGraphPlugins(...,
        @ProcessingOptions Map<String, String> processingOptions) {
    }
}

@Module
interface ProcessingEnvironmentModule{
    @Provides
    @ProcessingOptions
    static Map<String, String> processingOptions(XProcessingEnv xProcessingEnv) {
        return xProcessingEnv.getOptions();
    }

    @Provides
    static Map<String, String> getMap(String str) {
        return new Map<String,String>();
    }
} 

如上图所示,ExternalBindingGraphPlugins对象的实例化需要 Map<String, String> 对象,这里有两个Map<String,String>,那么需要靠@ProcessingOptions精确匹配。

  • 在容易产生类型混淆的情况下会用到@Qualifier修饰的注解,这个也非常容易理解。

@IntoSet、@IntoMap和@ElementsIntoSet

** @IntoSet、@IntoMap和@ElementsIntoSet用于修饰方法,规则如下**:

  1. 不允许同时使用,一个方法上仅仅可以使用前@IntoSet、@IntoMap和@ElementsIntoSet中的一个;

  2. 必须在bindingMethod绑定方法上使用,并且只支持@Provides、@Produces或@Binds三种类型的bindingMethod方法;

  3. 如果使用了@ElementsIntoSet,那么当前bindingMethod方法返回类型必须是Set< T>;

  4. @IntoMap 和@MapKey修饰的注解一定是成对出现的;

@IntoSet

收集同一种类型对象,下面给个案例了解下:

@Module
public interface BindingMethodValidatorsModule {

    //下面的都是该方法参数
    @Provides
    static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
            Set<BindingMethodValidator> validators) {
        return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
    }

    @Binds
    @IntoSet
    BindingMethodValidator provides(ProvidesMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator produces(ProducesMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator binds(BindsMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator multibinds(MultibindsMethodValidator validator);

    @Binds
    @IntoSet
    BindingMethodValidator bindsOptionalOf(BindsOptionalOfMethodValidator validator);
} 
  • @Binds修饰的bindingMethod方法有且仅有一个参数,并且参数类型一定是方法返回类型或返回类型子类。

如上所示,Set< BindingMethodValidator> validators用于收集所有的BindingMethodValidator类型对象;

延伸:这里的@Binds当然可以改成@Provides,e.g. :

 @Provides
    @IntoSet
    static BindingMethodValidator provides(XX xx){
        return new ProvidesMethodValidator(xx);
    } 

@ElementsIntoSet

和@IntoSet用法基本一致, @ElementsIntoSet返回类型必须是Set< T>,如上我们可以改成使用@ElementsIntoSet修饰:

@Module
public interface BindingMethodValidatorsModule {

    //下面的都是该方法参数
    @Provides
    static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
            Set<BindingMethodValidator> validators) {
        return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
    }

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> provides(ProvidesMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> produces(ProducesMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> binds(BindsMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> multibinds(MultibindsMethodValidator validator);

    @Binds
    @ElementsIntoSet
    Set<BindingMethodValidator> bindsOptionalOf(BindsOptionalOfMethodValidator validator);
} 
  • 还有一个,@ElementsIntoSet可以返回空绑定,但是@IntoSet不会。基本@IntoSet用的比较多,@ElementsIntoSet感觉像是一个附带产品。

@IntoMap

@IntoMap当然也是收集同一类对象,但是@IntoMap和@MapKey修饰的注解是成双成对出现的。why?@IntoMap是为了将当前类型T收集到Map<K,T>中,那么必须由@MapKey修饰的注解提供K

再来举个例子:

AndroidInjectionModule类:

@Beta
@Module
public abstract class AndroidInjectionModule {
  @Multibinds
  abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
      activityInjectorFactories();
    ...
} 

MainActivityModule_ContributeMainActivity类:

@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
  private MainActivityModule_ContributeMainActivity() {}

  @Binds
  @IntoMap
  @ActivityKey(MainActivity.class)
  abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
      MainActivitySubcomponent.Builder builder);

  @Subcomponent(modules = FragmentBuildersModule.class)
  public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
  }
} 

多提一句,这里的MainActivityModule_ContributeMainActivity是通过如下代码生成出来的:

@Suppress("unused")
@Module
abstract class MainActivityModule {
    @ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
    abstract fun contributeMainActivity(): MainActivity
} 

AndroidInjectionModule类的activityInjectorFactories方法用于收集用于收集Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>,MainActivityModule_ContributeMainActivity类中的bindAndroidInjectorFactory方法@ActivityKey(MainActivity.class)提供K:MainActivity.class,V:MainActivitySubcomponent.Builder,符合被收集的条件。

@MapKey

当前使用@Mapkey修饰的注解,满足如下规则:

  1. 如果MapKey.unwrapValue() = true的情况下(默认情况下就是true),被修饰的注解有且仅有一个方法,并且该方法返回类型不能是数组类型;
  • e.g.ClassKey注解
  1. 如果MapKey.unwrapValue() = false,那就麻烦了,被修饰的注解方法不但不限制,而且我们的项目中必须引入了com.google.auto.value.AutoAnnotation依赖,这是什么骚操作???
  • (1)引入AutoAnnotation的目的是对MapKey修饰的注解的所有方法返回类型作为变量生成一个新的类T,相当于MapKey.unwrapValue() = true的情况下的 @MapKey(T.class);

  • (2)所以MapKey.unwrapValue() = false可以使用MapKey.unwrapValue() = true替代,而且MapKey.unwrapValue() = true更容易理解、实现也更加稳健。非必要不要使用MapKey.unwrapValue() = false

  1. @MapKey和@IntoMap是情侣关系,必须在一起!!!

相关文章

网友评论

      本文标题:基于Dagger2.38.1版本全面理解注解-前言和部分注解

      本文链接:https://www.haomeiwen.com/subject/vmzwgrtx.html