Skip to main content

Resolving duplicate bindings

In this tutorial we will look at resolution of duplicate bindings using Multibinders.

Defining a multibinder

We can define multibinders to be exported from a module.

Module multibinderModule = ModuleBuilder.create()    .multibind(Key.of(Integer.class), Multibinders.ofBinaryOperator(Integer::sum))    .build();

Here we have defined a multibinder for Integers that simply adds all of the duplicate Integers together.

Let's create a module with multiple duplicate bindings for an Integer:

Module integersModule = ModuleBuilder.create()    .bind(Integer.class).toInstance(1)    .bind(Integer.class).toInstance(10)    .bind(Integer.class).toInstance(100)    .build();

Now let's create an Injector from these two modules and obtain an instance of an Integer:

Injector injector = Injector.of(multibinderModule, integersModule);System.out.println(injector.getInstance(Integer.class));

The result is 111 as the sum of 1, 10 and 100.

note

If we would try to create an Injector using only an integersModule without specifying a multibinder, we would get a Duplicate bindings error.

You can find example sources on GitHub

Resolution of Set<T> bindings

When you have multiple duplicate bindings for Set<T> you can automatically resolve them to a Set<T> binding that is a union of duplicate sets.

To do this you need to use multibindToSet(...) method of ModuleBuilder DSL.

Let's create 2 modules that define bindings for Set<Integer>:

Key<Set<Integer>> setKey = new Key<Set<Integer>>() {};
Module module1 = ModuleBuilder.create()    .bind(setKey).toInstance(new HashSet<>(Arrays.asList(1, 2, 3)))    .build();Module module2 = ModuleBuilder.create()    .bind(setKey).toInstance(new HashSet<>(Arrays.asList(3, 4, 5)))    .build();

A module1 defines a binding for a Set<Integer> that contains elements [1, 2, 3]. While module2 defines a binding for a Set<Integer> that contains elements [3, 4, 5].

Now let's create a module with a multibinder using ModuleBuilder#multibindToSet:

Module multibinderModule = ModuleBuilder.create()    .multibindToSet(Integer.class)    .build();

Finally, let's create an Injector from modules and obtain an instance of a Set<Integer>:

Injector injector = Injector.of(module1, module2, multibinderModule);System.out.println(injector.getInstance(setKey));

Obtained Set<Integer> contains a union of both sets: [1, 2, 3, 4, 5].

You can find example sources on GitHub

Resolution of Map<K, V> bindings

When you have multiple duplicate bindings for Map<K, V> you can automatically resolve them to a Map<K, V> binding that contains a union of keys and values of all the other maps. This is similar to resolution of Set<T> with an exception that conflicting maps cannot contain same keys.

To enable resolution of Map<K, V> bindings you nned to use multibindToMap(...) method of ModuleBuilder DSL.

Let's create 2 modules that define bindings for Map<Integer, String>:

Key<Map<Integer, String>> mapKey = new Key<Map<Integer, String>>() {};
Module module1 = ModuleBuilder.create()    .bind(mapKey).to(() -> {      Map<Integer, String> map = new HashMap<>();      map.put(1, "one");      map.put(2, "two");      map.put(3, "three");      return map;    })    .build();
Module module2 = ModuleBuilder.create()    .bind(mapKey).to(() -> {      Map<Integer, String> map = new HashMap<>();      map.put(4, "four");      map.put(5, "five");      map.put(6, "six");      return map;    })    .build();

A module1 defines a binding for a Map<Integer, String> that contains entries [1="one", 2="two", 3="three"]. While module2 defines a binding for a Map<Integer, String> that contains entries [4="four", 5="five", 6="six"]. Notice, that maps does not contain same keys.

Now let's create a module with a multibinder using ModuleBuilder#multibindToMap:

Module multibinderModule = ModuleBuilder.create()    .multibindToMap(Integer.class, String.class)    .build();

Finally, let's create an Injector from modules and obtain an instance of a Map<Integer, String>:

Injector injector = Injector.of(module1, module2, multibinderModule);System.out.println(injector.getInstance(mapKey));

Obtained Map<Integer, String> is a merged map that contains all of the entries of conflicting maps: [1="one", 2="two", 3="three", 4="four", 5="five", 6="six"].

You can find example sources on GitHub

@ProvidesIntoSet annotation

ActiveJ Inject supports a special @ProvidesIntoSet annotation that can be used in place of @Provides annotation. This annotation on a method with a return type T creates a binding of Set<T> and transforms a provided instance of T into a Set<T> that contains a single element [T].

When @ProvidesIntoSetannotation is used all the duplicate Set<T> bindings a resolved to a set that is a union of all the other provided sets of type T.

Let's define three modules that provide Integers using methods marked with @ProvidesIntoSet` annotations:

public static final class MyModule1 extends AbstractModule {  @ProvidesIntoSet  Integer integer() {    return 1;  }}
public static final class MyModule2 extends AbstractModule {  @ProvidesIntoSet  Integer integer() {    return 2;  }}
public static final class MyModule3 extends AbstractModule {  @ProvidesIntoSet  Integer integer() {    return 3;  }}

Finally, let's create an Injector from those 3 modules and obtain an instance of a Set<Integer>:

Module module1 = new MyModule1();Module module2 = new MyModule2();Module module3 = new MyModule3();Injector injector = Injector.of(module1, module2, module3);System.out.println(injector.getInstance(new Key<Set<Integer>>() {}));

When requesting an instance of Set<Integer> from the Injector we received a set containing elements [1, 2, 3].

You can find example sources on GitHub