Last week I started migrating an application that used Spring for DI to Google Guice when I stumbled on multibinding.
Since Google Guice 2.0 we can use Multibinding which allows us to bind multiple objects to a collection.
But the one thing I missed in the current release is the ability to bind objects with a specific annotation. So I thought, why not build it myself
So lets use the Multibinder example written in the Javadoc of Multibinder.
public class SnacksModule extends AbstractModule {
protected void configure() {
Multibinder multibinder = Multibinder.newSetBinder(binder(), ISnack.class);
multibinder.addBinding().toInstance(new Twix());
multibinder.addBinding().toProvider(SnickersProvider.class);
multibinder.addBinding().to(Skittles.class);
}
}
With this binding, a Set<ISnack> can now be injected:
class SnackMachine {
@Inject
public SnackMachine(Set snacks) { ... }
}
This is nice if you have a couple of Snacks, but if you have many and you want your
module clean and simple, you probably want something like this
multibind(ISnack.class).toAnnotatedClasses(Snack.class);
Where a Snack looks like this:
@Snack
public class Snickers implements ISnack { ... }
To make this possible, I created the following Module:
public abstract class AbstractMultibindModule extends AbstractModule {
public AdvancedMultibinder multibind(Class clazz) {
return new AdvancedMultibinder(clazz);
}
public class AdvancedMultibinder {
private Multibinder multibinder;
private Class clazz;
private AdvancedMultibinder(Class clazz) {
this.clazz = clazz;
multibinder = Multibinder.newSetBinder(binder(), clazz);
}
public Multibinder toAnnotatedClasses(Class bindingAnnotation) {
return toAnnotatedClasses(bindingAnnotation, clazz.getPackage().getName());
}
public Multibinder toAnnotatedClasses(Class bindingAnnotation, String basePackageName) {
for (Class clazz : AnnotationUtil.getAnnotatedClasses(bindingAnnotation, basePackageName)) {
multibinder.addBinding().to(clazz);
}
return multibinder;
}
public LinkedBindingBuilder addBinding() {
return multibinder.addBinding();
}
}
}
As you can see the multibind method returns an AdvancedMultibinder. We cannot extend Multibinder because it has a private Constructor, so instead I use the multibinder internally and call methods on it to add bindings.
The AdvancedMultibinder has a method toAnnotatedClasses, which takes the annotation class as a parameter and adds a binding for each annotated class.
Now we can do the following.
AdvancedMultibinder multibinder = multibind(ISnack.class);
multibinder.toAnnotatedClasses(Snack.class);
// Add a non annotated class to the bind.
multibinder.addBinding().to(Skittles.class);
bind(Machine.class).to(SnackMachine.class);
I have attached the example project here so you can look into the code yourself.
Filed under Java | 7 Comments »
Neato!
>> @Snack
>> public class Snickers implements ISnack
I don’t know anything about Guice, but it seems rather redundant. Isn’t it possible to specify something similar to:
AdvancedMultibinder multibinder = multibind(ISnack.class);
multibinder.toImplementingClasses(ISnack.class);
or even:
AdvancedMultibinder multibinder = multibind(ISnack.class);
multibinder.bindAllImplementingClasses();
Niels
I wish those were available in GIN. Could definetly use those!
That’s pretty nice. I have to agree with niels though, the parametrization of the collection doesn’t stack up against the redundancy of implementing the interface and annotate. But that’s easily fixed I think.
@niels and Iwein,
As you can see in the example I bind all Snacks with the Snack annotation. (maybe I should have called the annotation DefaultSnack)
multibinder.toAnnotatedClasses(Snack.class);
// Add a non annotated class to the bind.
multibinder.addBinding().to(Skittles.class);
And I add Skittles to my personal snackmachine which doesn’t have the annotation.
The downside of the toImplementingClasses would be that I can’t exclude any implementing class.
Great blog, I’ve one question why would you want to migrate from spring to guice?
@Erik, why not? Both Spring and Guice work great, but Guice makes your code cleaner and the setup is easier and more readable.