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:
MultibinderdemoPluginMultibinder = 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.
/** * Implementations to be bound to Guice multibinder set. */ interface DemoPlugin { void doWork(); }
/** * Random dependency to inject into the DemoPlugin implementations. */ class SomeRandomDependency { void doDependencyWork(String input) { System.out.println("Hello from the " + input + " plugin."); } }
/** * Red Team Plugin. */ class RedDemoPlugin implements DemoPlugin { private final SomeRandomDependency someRandomDependency; RedDemoPlugin(SomeRandomDependency someRandomDependency) { this.someRandomDependency = someRandomDependency; } @Override public void doWork() { someRandomDependency.doDependencyWork("Red"); } }
/** * Blue Team Plugin. */ class BlueDemoPlugin implements DemoPlugin { private final SomeRandomDependency someRandomDependency; BlueDemoPlugin(SomeRandomDependency someRandomDependency) { this.someRandomDependency = someRandomDependency; } @Override public void doWork() { someRandomDependency.doDependencyWork("Blue"); } }
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 Setplugins; @Inject DemoProcessor(Set plugins) { this.plugins = plugins; } void printPluginCount() { System.out.println("Plugin Count: " + plugins.size()); } void doWork() { plugins.forEach(DemoPlugin::doWork); } }
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.