[译]Dagger多重绑定

1,029 阅读7分钟

原文

Dagger允许您将多个对象绑定到一个集合中,即使对象使用多重绑定绑定到不同模块中时也是如此。Dagger组合集合,以便应用程序代码可以注入它而不依赖于单个绑定。

例如,您可以使用多重绑定实现插件体系结构,其中几个模块可以贡献单个插件接口实现,以便中心类可以使用整套插件。或者你可以有几个模块将个别服务提供商提供给Map,并以名称为关键字。

设置多绑定

为了将一个元素添加到可注入的多重绑定集中,请将@IntoSet注解添加到您的模块方法中:

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

您还可以通过添加返回子集并使用@ElementsIntoSet注解的模块方法一次提供多个元素:

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

现在,该组件中的绑定可以取决于该组:

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

或者组件可以提供这个集合:

@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>>

要为合格的多重集合做出贡献,请使用限定符对每个@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) { ... }
}

Map multibindings

只要在编译时知道映射键,Dagger就可以使用多重绑定将条目分配给可注入映射。

要为多边形映射贡献条目,请将方法添加到返回值的模块中,并使用@IntoMap进行注释,并使用另一个自定义批注指定该条目的映射键。 要为合格的多边形地图提供条目,请使用限定符对每个@IntoMap方法进行注释。

然后,您可以注入Map本身(Map<K,V>)或包含值提供程序的Map(Map<Provider>)。当你不希望所有的值都被实例化时,后者很有用,因为你一次要提取一个值,或者每次查询map时想要获取每个值的潜在新实例。

Simple map keys

对于键是字符串,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");
}

您的注释的单个成员可以是除数组之外的任何有效的注释成员类型,并且可以具有任何名称。

Complex map keys

如果您的map的关键字不仅可以由单个注释成员表示,则可以将整个注释用作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使用复杂key,那么您可能需要在运行时创建一个@MapKey注释实例,以传递给map的get(Object)方法。 最简单的方法是使用@AutoAnnotation注释来创建一个实例化注释的静态方法。 有关更多详细信息,请参阅@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);
  }
}

Maps whose keys are not known at compile time

只有在编译时已知map的关键字并且可以在注释中表示map多重绑定时才起作用。 如果地图的键不符合这些约束条件,则无法创建多重绑定map,但可以使用set multibindings绑定一组对象,然后将其转换为非多重绑定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<Foo,Provider>的自动绑定。如果您需要提供程序的地图,则多个绑定集中的Map.Entry对象应包含提供程序。然后,您的非多重绑定map可以具有提供者值。

@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 ...;
  }
}

声明多重绑定

您可以声明多绑定setmap是绑定的,方法是将一个@Multibinds注释的抽象方法添加到返回要声明的setmap的模块。

您不必为具有至少一个@IntoSet@ElementsIntoSet@IntoMap绑定的setmap使用@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();
}

给定的setmap多重绑定可以被声明任意次数而没有错误。Dagger从不实现或调用任何@Multibinds方法。

Alternative: @ElementsIntoSet returning an empty set

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

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

继承的子组件多重绑定

子组件中的绑定可以依赖于来自其父组件的多边集合或映射,就像它可以依赖于其父组件的任何其他绑定一样。但是,子组件可以将元素添加到绑定在其父项中的多对象集合或映射中,只需在其模块中包含相应的@Provides方法即可。

当发生这种情况时,根据注入位置的不同,组或地图会有所不同。当它被注入到子组件定义的绑定中时,它具有由子组件的多重绑定定义的值或条目以及由父组件的多重绑定定义的值或条目。 当它被注入到在父组件上定义的绑定中时,它只有在那里定义的值或条目。

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