Dagger 2从浅到深

Posted 行云间

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Dagger 2从浅到深相关的知识,希望对你有一定的参考价值。

Dagger系列:

  1. Dagger 2从浅到深(一)
  2. Dagger 2从浅到深(二)
  3. Dagger 2从浅到深(三)
  4. Dagger 2从浅到深(四)
  5. Dagger 2从浅到深(五)
  6. Dagger 2从浅到深(六)
  7. Dagger 2从浅到深(七)
  8. Dagger 2应用于Android的完美扩展库-dagger.android


Demo地址:

  1. DaggerLearn
  2. Kotlin-Dagger-2-Retrofit-Android-Architecture-Components

之前用到的,都是基于单个实例的依赖注入。强大的Dagger也支持多个元素的依赖注入,将注入的元素聚合到Set或者Map中,以便应用程序代码可以注入Set/Map,而不依赖于单独的绑定。

多个元素绑定并注入到Set

普通Set注入

将多个元素注入到Set中,不仅支持单个元素注入到Set中,同时支持子Set<T>注入到Set中。

  1. 将单个元素注入到Set:

    @Module()
    public class FruitModule {
    
        @Provides
        @IntoSet
        public BananaBean providerBanana() {
            return new BananaBean("特朗普香蕉");
        }
    }
    

    @Module()
    public class DrinkModule {

    @Provides
    @IntoSet
    public BananaBean providerBanana() {
        return new BananaBean("巴拿马香蕉");
    }
    

    }

  2. 将子Set<T>注入到Set:

    @Module()
    public class FruitModule {
        @Provides
        @ElementsIntoSet
        public Set<BananaBean> providerBananaSet() {
            Set<BananaBean> set = new HashSet<>();
            set.add(new BananaBean("布什香蕉"));
            set.add(new BananaBean("约翰逊香蕉"));
            return set;
        }
    }
    
  3. 在Component中,表明注入到Set的实例的提供Module。声明setBanana()方法,用来提供集合Set<BananaBean>

    @Component(modules = {FruitModule.class, DrinkModule.class})
    public interface FruitComponent {
    
        void inject(FruitActivity activity);
    
        Set<BananaBean> setBanana();
    }
    
  4. 声明类SetBananaBean,其成员变量为Set,同时@Inject注解的构造函数的参数类型为Set。

    public class SetBananaBean {
    
        Set<BananaBean> set;
    
        @Inject
        public SetBananaBean(Set<BananaBean> set) {
            this.set = set;
        }
    
        @Override
        public String toString() {
            return "SetBananaBean{" +
                    "set=" + set +
                    '}';
        }
    }
    
  5. 在FruitActivity类中

    • mSetBanana为通过依赖注入的Set<BananaBean> 实例;
    • mSetBananaBean为依赖注入的SetBananaBean实例,其成员变量为Set<BananaBean>
    • setBanana为FruitComponent所提供的Set<BananaBean> 实例

      public class FruitActivity extends AppCompatActivity {

      ***
      
      @Inject
      Set<BananaBean> mSetBanana;
      
      @Inject
      SetBananaBean mSetBananaBean;
      
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          FruitComponent fruitComponent = DaggerFruitComponent.builder()
                  .build();
          fruitComponent.inject(this);
          super.onCreate(savedInstanceState);
          setContentView(getLayoutId());
      
          ***
      
          Set<BananaBean> setBanana = fruitComponent.setBanana();
          //  [BananaBean{name='布什香蕉'}, BananaBean{name='约翰逊香蕉'}, BananaBean{name='特朗普香蕉'}, BananaBean{name='巴拿马香蕉'}]
          Log.d("test", "setBanana: " + setBanana.toString());
          // [BananaBean{name='巴拿马香蕉'}, BananaBean{name='布什香蕉'}, BananaBean{name='约翰逊香蕉'}, BananaBean{name='特朗普香蕉'}]
          Log.d("test", "mSetBanana: " + mSetBanana.toString());
          // [BananaBean{name='巴拿马香蕉'}, BananaBean{name='布什香蕉'}, BananaBean{name='约翰逊香蕉'}, BananaBean{name='特朗普香蕉'}] 
          Log.d("test", "mSetBananaBean: " + mSetBananaBean.toString());
      
      }
      
      ***
      

      }

从Log这里,我们可以看出,在该Component中,不仅可以提供Set的依赖注入,还可以给其他依赖注入的实例提供数据源。当然,还可以使用Component获取注入的Set实例。
这里写图片描述


注意:

与任何其他绑定一样,除了依赖注入Set<Foo>之外,还可以依赖注入Provider<Set<Foo>>或Lazy<Set<Foo >>,但是不能依赖Se<Provider<Foo>>。

特定Set注入

如果要为一个特定的集合注入数据,应对每个提供数据的Provides方法进行@Qualifier注解限定,从而避免了“依赖迷失”。在依赖注入该特定的Set时,也应使用相同的@Qualifier注解限定。

@Module
class MyModuleC {
  @Provides @IntoSet
  @MyQualifier
  static Foo provideOneFoo(DepA depA, DepB depB) {
    return new Foo(depA, depB);
  }
}

@Module
class MyModuleD {
  @Provides
  static FooSetUser provideFooSetUser(@MyQualifier Set<Foo> foos) { ... }
}

public class FruitActivity extends AppCompatActivity {

    ***

    @Inject
    @MyQualifier
    Set<BananaBean> mProvinceSetBanana;

    ***
}

多个元素绑定并注入到Map #

Dagger不仅可以将绑定的多个元素依赖注入到Set,还可以将绑定的多个元素依赖注入到Map。与依赖注入Set不同的是,依赖注入Map时,必须在编译时指定Map的Key,那么Dagger向MapMap注入相应的元素。

对于向Map提供元素的@Provides方法,需要使用@IntoMap,同时指定该元素的Key(例如@StringKey(“foo”)、@ClassKey(Thing.class))。

在Dagger中,Map的Key可以分为简单的和组合的两种情况,对于简单的或者组合的Key是怎么区分呢?下面以实例来理解。

简单的Key

所谓简单的Key,是指Map的Key的数据类型为单一的某一种数据类型,也就是说Key的数据类型必须一致,通常我们使用Map也就是这么使用的。

如果注入的Map的Key的类型为String、Class<?>等,在dagger.multibindings中的提供了一套标准的注解:

  • ClassKey
  • IntKey
  • LongKey
  • StringKey

我们先以StringKey、ClassKey注解为例注入Map:

  1. 在Module中定义提供元素的@Provides方法

    @Module()
    public class DrinkModule {
    
        @Provides
        @IntoMap // 指定该@Provides方法向Map提供元素
        @StringKey("A") // 指定该元素在Map中所对应的的Key
        public AppleBean providerApple() {
            return new AppleBean("A苹果");
        }
    
    
        @Provides
        @IntoMap
        @ClassKey(DrinkActivity.class)
        public AppleBean providerAppleMap() {
            return new AppleBean("北京苹果");
        }
    }
    
  2. 在Component中,定义提供注入的Map实例的方法

    @Component(modules = {DrinkModule.class})
    public interface DrinkComponent {
        void inject(DrinkActivity activity);
    
        Map<String, AppleBean> appleByString();
    
        Map<Class<?>, AppleBean> appleByClass();
    }
    
  3. 在DrinkActivity类中,获取依赖的Map实例有两种方式:

    • 使用@Inject,注入Map
    • 调用Component定义的方法

      public class DrinkActivity extends AppCompatActivity {
      
          ***
      
          @Inject
          Map<String, AppleBean> appleByString;
          @Inject
          Map<Class<?>, AppleBean> appleByClass;
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              DrinkComponent drinkComponent = DaggerDrinkComponent.builder()
                      .build();
              drinkComponent.inject(this);
      
              ***
      
              // 依赖注入Map
              // appleByString: {A=AppleBean{name='A苹果'}}
              Log.d("test", "appleByString: " + appleByString);
              // appleByClass: {class advanced.todo.com.daggerlearn.activity.DrinkActivity=AppleBean{name='北京苹果'}}
              Log.d("test", "appleByClass: " + appleByClass);
      
              // 组件提供Map实例
              // appleByString: {A=AppleBean{name='A苹果'}}
              Log.d("test", "appleByString: " + drinkComponent.appleByString());
              // appleByClass: {class advanced.todo.com.daggerlearn.activity.DrinkActivity=AppleBean{name='北京苹果'}}
              Log.d("test", "appleByClass:" + drinkComponent.appleByClass());
          }
      
          ***
      }
      

自定义Key

在实际开发过程中,Map的Key的数据类型千变万化,Google大神也不能一一提供,只是针对常用的Key提供了注解。那么问题就来了,dagger.multibindings定义的Key的注解毕竟有限,如果注入到的Map的Key的数据类型为Apple,该怎么办呢?

@Documented
@Target(METHOD)
@Retention(RUNTIME)
@MapKey
public @interface StringKey {
  String value();
}

先看下,StringKey的源码,StringKey的value类型为String,应该是指定了Key的数据类型为String。而StringKey又被@MapKey注解,是不是表明该注解是Map的Key的注解呢?不妨试一下,我们自定义一个AppleKey:

@Documented
@Target(METHOD)
@Retention(RUNTIME)
@MapKey
public @interface AppleKey {
    AppleBean value();
}

可是,编译器直接报错了:“Invalid type ‘AppleBean’ for annotation member”,其意思是类型为“AppleBean”的注释成员无效,也就是这种定义方式可行,为什么呢?

这里写图片描述

section 9.6.1 of the JLS中,明确指定了在注释类型中声明的方法的返回类型,如果不满足指定的返回类型,那么编译时会报错:

  • 基本数据类型
  • String
  • Class
  • 枚举类型
  • 注解类型
  • 以上数据类型的数组

这里写图片描述

如果注入Map的Key为枚举或者具体参数化类,应自定义Map的key的数据类型的注解,并使用@MapKey注解。

enum MyEnum {
  ABC, DEF;
}

@MapKey
@interface MyEnumKey {
  MyEnum value();
}

@MapKey
@interface MyNumberClassKey {
  Class<? extends Number> value();
}

@Module
class MyModule {
  @Provides @IntoMap
  @MyEnumKey(MyEnum.ABC)
  static String provideABCValue() {
    return "value for ABC";
  }

  @Provides @IntoMap
  @MyNumberClassKey(BigDecimal.class)
  static String provideBigDecimalValue() {
    return "value for BigDecimal";
  }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<MyEnum, String> myEnumStringMap();
  Map<Class<? extends Number>, String> stringsByNumberClass();
}

@Test void testMyComponent() {
  MyComponent myComponent = DaggerMyComponent.create();
  assertThat(myComponent.myEnumStringMap().get(MyEnum.ABC)).isEqualTo("value for ABC");
  assertThat(myComponent.stringsByNumberClass.get(BigDecimal.class))
      .isEqualTo("value for BigDecimal");
}

对于自定义的Key的数据类型的注解,其数据类型可以为除数组以外的任何有效的注解的成员类型.当然,名称可以随意定义。

组合的Map的Key

组合的Map的Key,是指Map的Key由多个数据类型的成员组成。这与我们所熟知的Map<K, V>是相悖的,因为Key的数据类型是指定的K,怎么还会出现多种数据类型呢?

我们先以一个例子,看看组合的Key到底是个什么东东?

@MapKey(unwrapValue = false)
@interface MyKey {
  String name();
  Class<?> implementingClass();
  int[] thresholds();
}

@Module
class MyModule {
  @Provides @IntoMap
  @MyKey(name = "abc", implementingClass = Abc.class, thresholds = {1, 5, 10})
  static String provideAbc1510Value() {
    return "foo";
  }
}

@Component(modules = MyModule.class)
interface MyComponent {
  Map<MyKey, String> myKeyStringMap();
}



class MyComponentTest {
  @Test void testMyComponent() {
    MyComponent myComponent = DaggerMyComponent.create();
    assertThat(myComponent.myKeyStringMap()
        .get(createMyKey("abc", Abc.class, new int[] {1, 5, 10}))
        .isEqualTo("foo");
  }

  @AutoAnnotation
  static MyKey createMyKey(String name, Class<?> implementingClass, int[] thresholds) {
    return new AutoAnnotation_MyComponentTest_createMyKey(name, implementingClass, thresholds);
  }
}

这里把官方的例子搬了过来,但是在创建的时候,一直编译未通过,也找不到啥原因,好想哭的感觉。这个东东好坑爹,先放这里,知道有这个功能就好了。

编译时,Map的Key未指定

在编译时,注入的Map的Key是指定的,也就是已知的,同时在注解中标明,此时Map的多元素绑定才能正常执行。如果Map的Key不满足这些约束条件,那么将无法创建一个多对多的Map。但是,可以通过设置多个绑定来绑定一组对象,然后将其转换为一个非多对多的Map。

@Module
class MyModule {
  @Provides @IntoSet
  static Map.Entry<Foo, Bar> entryOne(...) {
    Foo key = ...;
    Bar value = ...;
    return new SimpleImmutableEntry(key, value);
  }

  @Provides @IntoSet
  static Map.Entry<Foo, Bar> entryTwo(...) {
    Foo key = ...;
    Bar value = ...;
    return new SimpleImmutableEntry(key, value);
  }
}

@Module
class MyMapModule {
  @Provides
  static Map<Foo, Bar> fooBarMap(Set<Map.Entry<Foo, Bar>> entries) {
    Map<Foo, Bar> fooBarMap = new LinkedHashMap<>(entries.size());
    for (Map.Entry<Foo, Bar> entry : entries) {
      fooBarMap.put(entry.getKey(), entry.getValue());
    }
    return fooBarMap;
  }
}


值得注意的是,这种情况下,Dagger并不会自动注入Map<Foo,Provider<Bar>>,也就意味着不会提供一个含有Provider值得Map。如果想要获取一个含有Provider的Map,需要在Map.Entry对象中包含Provider值,那么所获取的Map也就含有Provider。

@Module
class MyModule {
  @Provides @IntoSet
  static Map.Entry<Foo, Provider<Bar>> entry(
      Provider<BarSubclass> barSubclassProvider) {
    Foo key = ...;
    return new SimpleImmutableEntry(key, barSubclassProvider);
  }
}

@Module
class MyProviderMapModule {
  @Provides
  static Map<Foo, Provider<Bar>> fooBarProviderMap(
      Set<Map.Entry<Foo, Provider<Bar>>> entries) {
    return ...;
  }
}

声明多重绑定

可以声明一个多重绑定的Set或者Map,其方法是在Module中,添加一个由@Meeibinds注解的抽象方法,该方法的返回值为声明的Set或者Map.

对于注入的Set或者Map不一定必须使用@Meeibinds注解,可以使用@IntoSet,@ElementsIntoSet或@IntoMap等任何一种方式注解。如果它们为空,必须声明至少一个。

@Module
abstract class MyModule {
  @Multibinds abstract Set<Foo> aSet();
  @Multibinds @MyQualifier abstract Set<Foo> aQualifiedSet();
  @Multibinds abstract Map<String, Foo> aMap();
  @Multibinds @MyQualifier abstract Map<String, Foo> aQualifiedMap();
}

给定的Set或Map多重绑定可以声明任意次数而不会发生错误。Dagger从不实现或调用任何@Multibinds方法。

替代方法:@ElementsIntoSet返回一个空集

仅对于空Set,作为替代,您可以添加返回空集合的@ElementsIntoSet方法

@Module
class MyEmptySetModule {
  @Provides @ElementsIntoSet
  static Set<Foo> primeEmptyFooSet() {
    return Collections.emptySet();
  }
}

以上是关于Dagger 2从浅到深的主要内容,如果未能解决你的问题,请参考以下文章

从浅到深认识rocketmq

git一站式学习git跟进案例,从浅到深

31 天,从浅到深轻松学习 Kotlin

从浅到深使用pm2来彻底掌握微服务运维精髓

从浅到深使用pm2来彻底掌握微服务运维精髓

由浅到深理解ROS