Quantcast
Channel: Coding - John Chapman
Viewing all articles
Browse latest Browse all 23

[Guice | Java] Using Guice 4.0 Multibinder with @Provides and @ProvidesIntoSet (or @ProvidesIntoMap)

$
0
0

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.

/**
 * 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 Set plugins;
    @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.

Viewing all articles
Browse latest Browse all 23

Latest Images

Trending Articles





Latest Images