Elite Guice 2: Binding de-duplication
In part one, I showed how to initialize a Guice extension using the@Inject
tag on a method. In this post I'm going to demonstrate a Guice for deduplicating bindings.What is binding duplication?
One of the features of the Multibindings Extension is that there's no central coordination. Each module contributes its own bindings, and they all get amalgamated into a single collection. Suppose you have these modules:
public class CandybarModule extends AbstractModule {
protected void configure() {
Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().toInstance(new Twix());
multibinder.addBinding().toProvider(SnickersProvider.class);
}
}
public class ChipsModule extends AbstractModule {
protected void configure() {
Multibinder<Snack> multibinder = Multibinder.newSetBinder(binder(), Snack.class);
multibinder.addBinding().to(Pringles.class);
multibinder.addBinding().toInstance(new Doritos());
}
}
Out of this API, our
Multibinder
implementation needs to bind Set<Snack>
exactly once. This is tricky! For example, suppose our newSetBinder
method was implemented like this: public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
binder.bind(getSetKey(type)).toProvider(getSetProvider(type));
return new Multibinder(type);
}
This won't work. We call
newSetProvider
from both the CandybarModule
and the ChipsModule
, and Guice complains because we're binding Set<Snack>
twice. This is binding duplication.If Guice had an duplicate-binding API
Guice doesn't have this API, but what we kind of find ourselves wanting is a way to query the binder for its bindings thus-far:
public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
if (!binder.getBindings().containsKey(getSetKey(type))) {
binder.bind(getSetKey(type)).toProvider(getSetProvider(type));
}
return new Multibinder(type);
}
This would work, but in general it's a pretty terrible idea. It makes it easy to write fragile modules - modules that depend on the order they are installed in. And it also changes the module from being static configuration (good!) to a dynamic-runtime configuration (bad!). So we're glad this API doesn't exist.
But in this one case, we really wish we could prevent our binding from being performed twice.
Modules as value objects
The solution to this problem is a bit surprising, but I think it's quite elegant. Guice conveniently allows the same module to be installed twice. This is necessary so that both your
PizzaServletsModule
and your PizzaDatabaseModule
can install your PizzaDomainModule
. Rather than binding the
Set<Snack>
directly on the binder, we create special Module
class whose only job is to create that binding. And then we give that module a proper equals()
and hashCode()
methods, so that any two instances that bind the same type are equal.Our final code looks like this:
public static <T> Multibinder<T> newSetBinder(Binder binder, Type type) {
binder.install(new MultibindingModule(getSetType(), getProviderType()));
return new Multibinder(type);
}
The
Module
trick allows us to decentralize the binding. And that means less configuration code, and that means less code. Hooray for Guice.