翻译Dagger 2多元绑定

作者: lanceJin | 来源:发表于2017-10-09 01:10 被阅读48次

    官方文档链接:https://google.github.io/dagger/multibindings.html

    1.前言


    Dagger通过多元绑定可以将几个对象放入一个集合中,即使它们是由不同的Module提供。集合的组装由Dagger自己完成,所以往代码中注入时,不需要直接依赖于每个对象。使用多元绑定可以实现一个插件架构,举个例子,由几个Moule分别提供插件接口的实现,核心类就可以使用一整套的插件。或者将几个Module分别提供的Service,以名字为键放入Map。

    2.Set的使用


    为了给可注入的Set提供元素,需为Module的方法添加@IntoSet注解:

    @Module
    class MyModuleA {
      @Provides @IntoSet
      static String provideOneString(DepA depA, DepB depB) {
        return "ABC";
      }
    }
    

    若想一次性提供多个元素,可增加返回值是一个Set的Module方法,并且注解为@ElementsIntoSet

    @Module
    class MyModuleB {
      @Provides @ElementsIntoSet
      static Set<String> provideSomeStrings(DepA depA, DepB depB) {
        return new HashSet<String>(Arrays.asList("DEF", "GHI"));
      }
    }
    

    在Component的依赖图中,可直接依赖Set:

    class Bar {
      @Inject Bar(Set<String> strings) {
        assert strings.contains("ABC");
        assert strings.contains("DEF");
        assert strings.contains("GHI");
      }
    }
    

    或者通过Component向外提供Set:

    @Component(modules = {MyModuleA.class, MyModuleB.class})
    interface MyComponent {
      Set<String> strings();
    }
    
    @Test void testMyComponent() {
      MyComponent myComponent = DaggerMyComponent.create();
      assertThat(myComponent.strings()).containsExactly("ABC", "DEF", "GHI");
    }
    

    与其它依赖关系一样,除了提供Set<Foo>,还有Provider<Set<Foo>>Lazy<Set<Foo>>。但是,不能依赖Set<Provider<Foo>>。为了提供修饰过的Set,给@Provides注解的方法添加修饰注解:

    @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) { ... }
    }
    

    3.Map的使用


    Dagger通过多元绑定给可注入的Map提供实例,只要Map的键在编译时被知道。与Set有点不同,除了添加能返回值的Module方法,并用@IntoMap注解,还需要另一个自定义的注解来给提供的实例指定Map的键。若是给修饰过的Map提供实例,须给@IntoMap注解的方法添加修饰注解。

    可以注入Map本身(Map<K, V>)或者值y用被Provider包裹的Map(Map<K, Provider<V>>)。后者可以实现集合中的值不全部初始化,比如希望每次获取一个值,或者希望每次查询Map时,即使同一个值也是新的对象。

    3.1.简单的键

    当Map的键是字符串、Class<?>或者装箱的基本类型时,使用dagger.multibindings包中的标准注解:

    @Module
    class MyModule {
      @Provides @IntoMap
      @StringKey("foo")
      static Long provideFooValue() {
        return 100L;
      }
    
      @Provides @IntoMap
      @ClassKey(Thing.class)
      static String provideThingValue() {
        return "value for Thing";
      }
    }
    
    @Component(modules = MyModule.class)
    interface MyComponent {
      Map<String, Long> longsByString();
      Map<Class<?>, String> stringsByClass();
    }
    
    @Test void testMyComponent() {
      MyComponent myComponent = DaggerMyComponent.create();
      assertThat(myComponent.longsByString().get("foo")).isEqualTo(100L);
      assertThat(myComponent.stringsByClass().get(Thing.class))
          .isEqualTo("value for Thing");
    }
    

    若键是枚举类型或具体的参数化类,则声明一个@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");
    }
    

    自定义的注解可以是任何名字,它的唯一成员可以是除数组外的任何有效的注解成员类型。

    3.2.复杂的键

    如果Map的键需要不只单个注解成员才能表达,可通过给自定义注解的@MapKey注解设置unwrapValue = false,来使用整个注解(包括多个注解成员)作为键。此时,自定义的注解可以拥有数组成员。

    @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();
    }
    

    使用@AutoAnnotation创建注解实例。如果使用复杂的键,需要在运行时给自定义的注解创建实例,并传入Map的get(Object)方法中。最简单的办法是,添加静态方法返回自定义注解的实例,并使用@AutoAnnotation注解此方法。

    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);
      }
    }
    
    3.3.编译时不知道键

    Map只在编译时已知键且用注解表示出来的情况下,才会工作。如果不满足这些限制,就不能直接使用,但是可以通过Set存储一系列的对象,然后转换成普通的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;
      }
    }
    

    通过这种方法,若想普通Map中含有Provider包裹的值,构建Set的Map.Entry对象也应该包含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 ...;
      }
    }
    

    4.声明多元绑定


    通过给Module增加抽象的、被@Multibinds注解的方法,并返回希望声明的Set或Map。若Set或Map已经被@IntoSet@ElementsIntoSet@IntoMap中至少一个注解,那么不应该使用@Multibinds注解,除非它们是空集合。

    @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注解的方法。

    5.返回空Set


    作为一种针对空Set的备选的解决方案,添加@ElementsIntoSet注解的返回空Set的方法。

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

    6.继承Subcomponent的多元绑定


    Subcomponent可以依赖从它父Component那继承到的Set和Map,就像可以依赖它父Component的其它关系一样。但是,Subcomponent可以通过给Module添加适当的@Provides注解的方法,来给继承到的Set和Map添加元素。此时,Set和Map不同于注入时,除了含有Subcomponent定义的值或Map.Entry,同时还包含父Component定义的。

    @Component(modules = ParentModule.class)
    interface ParentComponent {
      Set<String> strings();
      Map<String, String> stringMap();
      ChildComponent childComponent();
    }
    
    @Module
    class ParentModule {
      @Provides @IntoSet
      static String string1() {
        "parent string 1";
      }
    
      @Provides @IntoSet
      static String string2() {
        "parent string 2";
      }
    
      @Provides @IntoMap
      @StringKey("a")
      static String stringA() {
        "parent string A";
      }
    
      @Provides @IntoMap
      @StringKey("b")
      static String stringB() {
        "parent string B";
      }
    }
    
    @Subcomponent(modules = ChildModule.class)
    interface ChildComponent {
      Set<String> strings();
      Map<String, String> stringMap();
    }
    
    @Module
    class ChildModule {
      @Provides @IntoSet
      static String string3() {
        "child string 3";
      }
    
      @Provides @IntoSet
      static String string4() {
        "child string 4";
      }
    
      @Provides @IntoMap
      @StringKey("c")
      static String stringC() {
        "child string C";
      }
    
      @Provides @IntoMap
      @StringKey("d")
      static String stringD() {
        "child string D";
      }
    }
    
    @Test void testMultibindings() {
      ParentComponent parentComponent = DaggerParentComponent.create();
      assertThat(parentComponent.strings()).containsExactly(
          "parent string 1", "parent string 2");
      assertThat(parentComponent.stringMap().keySet()).containsExactly("a", "b");
    
      ChildComponent childComponent = parentComponent.childComponent();
      assertThat(childComponent.strings()).containsExactly(
          "parent string 1", "parent string 2", "child string 3", "child string 4");
      assertThat(childComponent.stringMap().keySet()).containsExactly(
          "a", "b", "c", "d");
    }
    

    7.总结


    使用多元绑定时,会让集合出现的莫名其妙,对其中的元素也很难掌握清楚。建议根据个人的情况进行使用,但跨Module供值的确是优势。

    相关文章

      网友评论

        本文标题:翻译Dagger 2多元绑定

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