About Guice
Guice is a dependency injection framework for Java. For more information, see https://github.com/google/guice.
Introduction
Prior to the 4.0 release of Guice, creating a Set
or Map
of bindings to inject was accomplished using a Multibinder
(JavaDoc) as follows:
Multibinder demoPluginMultibinder = Multibinder.newSetBinder(binder(), DemoPlugin.class);
demoPluginMultibinder.addBinding().to(BlueDemoPlugin.class);
demoPluginMultibinder.addBinding().to(RedDemoPlugin.class);
The limitation to this approach include having to add all of the bindings to the multibinder within one Guice module (or having to pass around the Multibinder
between Guice modules at configure-time).
With Guice 4.0, a new method has been added to creating a Set
or Map
of bindings: the @ProvidesIntoSet
(JavaDoc) and @ProvidesIntoMap
(JavaDoc) annotations along with the MultibindingsScanner
(JavaDoc). These have simplified the multibinding process as follows:
@Override
protected void configure() {
install(MultibindingsScanner.asModule());
}
@ProvidesIntoSet
DemoPlugin getBlueDemoPlugin() {
return new BlueDemoPlugin(new SomeRandomDependency());
}
@ProvidesIntoSet
DemoPlugin getRedDemoPlugin() {
return new RedDemoPlugin(new SomeRandomDependency());
}
Using the annotations and the MultibindingsScanner
allows for the bindings to be separated out into multiple modules without having to pass around or explicitly create a Multibinder
. Existing examples (such as the Guice unit tests, ProvidesIntoTest.java) illustrate how to use this methodology with instances created at configure-time rather than run-time. This becomes complicated when those instances have dependencies that are created and bound elsewhere. The example below illustrates how to use these annotations without requiring configure-time instantiation.
Multibinding with @Provides
and @ProvidesIntoSet
(or @ProvidesIntoMap
)
Rather than instantiating at configure-time with @ProvidesIntoSet
, we can use a @Provides
annotated method to construct the class after configuring the Guice-modules, and after all of the required dependencies have been bound.
@Provides
BlueDemoPlugin provideBlueDemoPlugin(SomeRandomDependency someRandomDependency) {
return new BlueDemoPlugin(someRandomDependency);
}
@ProvidesIntoSet
DemoPlugin getBlueDemoPlugin(BlueDemoPlugin blueDemoPlugin) {
return blueDemoPlugin;
}
@Provides
RedDemoPlugin provideRedDemoPlugin(SomeRandomDependency someRandomDependency) {
return new RedDemoPlugin(someRandomDependency);
}
@ProvidesIntoSet
DemoPlugin getRedDemoPlugin(RedDemoPlugin redDemoPlugin) {
return redDemoPlugin;
}
With the sample above, we are relying on Guice to configure all modules and resolve all dependencies before our DemoPlugin
implementations are instantiated. This allows us to create our Multibinder
without having to have all of the dependencies instantiated before binding.
Sample Project
The following code snippets put this all together for a sample project.
DemoPlugin.java
/**
* Implementations to be bound to Guice multibinder set.
*/
interface DemoPlugin {
void doWork();
}
SomeRandomDependency.java
/**
* Random dependency to inject into the DemoPlugin implementations.
*/
class SomeRandomDependency {
void doDependencyWork(String input) {
System.out.println("Hello from the " + input + " plugin.");
}
}
RedDemoPlugin.java
/**
* Red Team Plugin.
*/
class RedDemoPlugin implements DemoPlugin {
private final SomeRandomDependency someRandomDependency;
RedDemoPlugin(SomeRandomDependency someRandomDependency) {
this.someRandomDependency = someRandomDependency;
}
@Override
public void doWork() {
someRandomDependency.doDependencyWork("Red");
}
}
BlueDemoPlugin.java
/**
* Blue Team Plugin.
*/
class BlueDemoPlugin implements DemoPlugin {
private final SomeRandomDependency someRandomDependency;
BlueDemoPlugin(SomeRandomDependency someRandomDependency) {
this.someRandomDependency = someRandomDependency;
}
@Override
public void doWork() {
someRandomDependency.doDependencyWork("Blue");
}
}
DemoProcessor.java
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Set;
/**
* Class Guice multibinder set injected into.
*/
@Singleton
class DemoProcessor {
private final Set plugins;
@Inject
DemoProcessor(Set plugins) {
this.plugins = plugins;
}
void printPluginCount() {
System.out.println("Plugin Count: " + plugins.size());
}
void doWork() {
plugins.forEach(DemoPlugin::doWork);
}
}
DemoGuiceModule.java
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.multibindings.MultibindingsScanner;
import com.google.inject.multibindings.ProvidesIntoSet;
/**
* Guice module to bind multibinder set with @ProvidesIntoSet.
*/
class DemoGuiceModule extends AbstractModule {
@Override
protected void configure() {
install(MultibindingsScanner.asModule());
bind(SomeRandomDependency.class);
bind(DemoProcessor.class);
}
@Provides
BlueDemoPlugin provideBlueDemoPlugin(SomeRandomDependency someRandomDependency) {
return new BlueDemoPlugin(someRandomDependency);
}
@ProvidesIntoSet
DemoPlugin getBlueDemoPlugin(BlueDemoPlugin blueDemoPlugin) {
return blueDemoPlugin;
}
@Provides
RedDemoPlugin provideRedDemoPlugin(SomeRandomDependency someRandomDependency) {
return new RedDemoPlugin(someRandomDependency);
}
@ProvidesIntoSet
DemoPlugin getRedDemoPlugin(RedDemoPlugin redDemoPlugin) {
return redDemoPlugin;
}
}
Testing the Sample Code
We can instantiate a Guice injector for the DemoGuiceModule
to validate our Multibinder
:
import com.google.inject.Guice;
import com.google.inject.Injector;
public class Main {
public static void main(String[] args) {
Injector injector = Guice.createInjector(new DemoGuiceModule());
DemoProcessor processor = injector.getInstance(DemoProcessor.class);
processor.printPluginCount();
processor.doWork();
}
}
Output
Plugin Count: 2
Hello from the Blue plugin.
Hello from the Red plugin.
The post Using Guice 4.0 Multibinder with @Provides and @ProvidesIntoSet (or @ProvidesIntoMap) first appeared on John Chapman.