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 up all duplicate Integers together.

Let's create a module with several duplicate bindings for 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 Integer instance:

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 tried to create an Injector using only the 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 into a Set<T> binding, which is a union of duplicate sets.

To do this you need to use the 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 the Set<Integer> which contains elements [1, 2, 3]. module2 defines a binding for the Set<Integer> which 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 the keys and values of all other maps. This is similar to resolving Set<T> with the exception that conflicting maps cannot contain the same keys.

To enable Map<K, V> binding resolution, you must use the multibindToMap(...) method of ModuleBuilder DSL.

Let's create 2 modules defininig 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 do 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 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 which can be used instead of @Provides annotation. This annotation on a method with the return type T creates a Set<T> binding and transforms the provided T instance into a Set<T> containing a single [T] element.

With@ProvidesIntoSet annotation, all duplicate Set<T> bindings are resolved into a set, which is the union of all other provided sets of type T.

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

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 three modules and obtain an instance of 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 we requested an instance of Set<Integer> from the Injector, we received a set containing elements [1, 2, 3].

You can find example sources on GitHub