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

Module module1 = ModuleBuilder.create()
.bind(setKey).toInstance(Set.of(1, 2, 3))
.build();
Module module2 = ModuleBuilder.create()
.bind(setKey).toInstance(Set.of(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<>() {};

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