6/16/2009
Multi bind in Guice 2.0
Guice is great tool to do dependency injection. But when you need to bind more than one implementation, or bind more than one instance, or bind a collection, things will be become tricky. After fighting with Guice for a long time, I think it is worth a while to document the tricks I've found.
Bind mutliple instances
Given we have two database to connect in my project. This code will not workbind(SqlMapClient.class).toInstance(createSqlMapClientForDB1()); bind(SqlMapClient.class).toInstance(createSqlMapClientForDB2());It will not work not only because Guice do not allow you bind same key(SqlMapClient.class is the key in this case) twice, but also when we use the dependency.
public class Service1 { @Inject SqlMapClient sqlMapClient; }How can we know which database connection we've got? This is a well-known problem, and has been addressed since the 1.0. In 1.0, we have three choices to make it work:
Choice 1: Different Type
We can use inheritance to make two SqlMapClient instances of different type.public class Db1SqlMapClient extends SqlMapClient { private final SqlMapClient delegate; // delegate all methods of sql map client } public class Service1 { @Inject Db1SqlMapClient sqlMapClient; }
Choice 2: Binding Annotation
Key in Guice is not necessary the type itself, it could be type and binding annotation. Use binding annotation, we can bind same type multiple times, although each binding is still using different key (different binding annotation).@BindingAnnotation @Retention(RetentionPolicy.RUNTIME public @interface DB1 { } bind(SqlMapClient.class) .annotatedWith(DB1.class) .with(createSqlMapClientForDB1()); public class Service1 { @Inject @DB1 SqlMapClient sqlMapClient; }
Choice 3: Named Binding
Guice pre-defined a binding annotation called "Named". We can use "Names.named()" to create a instance of it.bind(SqlMapClient.class) .annotatedWith(Names.named("DB1")) .with(createSqlMapClientForDB1()); public class Service1 { @Inject @Named("DB1") SqlMapClient sqlMapClient; }Although we have three choices, none of them is perfect. The first one is very tedious, and also the user of the SqlMapClient need to know the concrete type. The second one is better, but still the user need to know which one it depends on by annotate its dependency. Still kinda of violated the principle of "Inversion of Control". Also, we need to define one more class for binding annotation. The third choice do not need us to define new class, but it is not refactoring friendly, and can not be found by finding references. So the recommanded way to do that is strong typed binding annotation.
Choice 4: Guice 2.0 Child Injector
In Guice 2.0, we can use child injector to define different binding to the same type.db1Injector = injector.createChildInjector(new AbstractModule() { public void configure() { bind(SqlMapClient.class).toInstance(createSqlMapClientForDB1()); } }); db2Injector = injector.createChildInjector(new AbstractModule() { public void configure() { bind(SqlMapClient.class).toInstance(createSqlMapClientForDB2()); } });Different database connection need to be injected by different injector. To use this style, your system has to be partitioned to be managed by several different containers. It is not practical in real world.
Bind Set
It seems easy, isn't it?bind(Set.class).toInstance(new HashSet());But what if we need to bind two set, one for set of integer, another for set of string. How to do that?
bind(new TypeLiteral<Set<String>>(){}).toInstance(new HashSet<String>(){{ add("Hello"); add("World"); }});Or we can use the Types utility class introduced in Guice 2.0.
bind(Types.setOf(String.class)).toInstance(new HashSet<String>(){{ add("Hello"); add("World"); }});This also seems easy. But how about the element of the set is not just a simple String. What if we have a interface called OrderProcessor:
public interface OrderProcessor { void processOrder(Order order); }Then we can have different OrderProcessor to process the order differently (send email, save the order into database):
public class MailOrderProcessor implements OrderProcessor { @Inject EmailSender emailSender // send mail }
public class DbOrderProcessor implements OrderProcessor { @Inject SqlMapClient sqlMapClient; // save order to database }Ok, now how to bind set of order processor? Can we do this?
bind(Types.of(Set.class)).toInstance(new HashSet<OrderProcessor>(){{ add(new MailOrderProcessor()); add(new DbOrderProcessor()); }});No, you can't. Because both of them have its own dependency. Manually newed instance will not inject those dependencies. To make it work, we have four choices:
Choice 1: Manually call injectMembers
@Inject Injector injector; for (OrderProcessor orderProcessor : orderProcessors) { injector.injectMemebers(orderProcess); }
Choice 2: Wrapping the set
public class OrderProcessors { private final Setprocessors = new HashSet (); @Inject public void setDbOrderProcessor(DbOrderProcessor processor) { processors.add(processor); } @Inject public void setMailOrderProcessor(MailOrderProcessor processor) { processors.add(processor); } }
Choice 3: using getProvider
bind(new TypeLiteral<Set<Provider<OrderProcessor>>>(){}).toInstance(new HashSet<Provider<OrderProcessor>>(){{ add(getProvider(DbOrderProcessor.class); add(getProvider(MailOrderProcessor.class); }});Here, we used the feature of AbstractModule called getProvider. Although we can not call injector.getInstance() inside a module, but we can get the provider of the instance. This way, what we got is a set of the provider of the processor, instead of a set of order processor. This might not what you want.
Choice 3: getProvider + ProvidedOrderProcessor
public class ProvidedOrderProcessor implements OrderProcessor { private final Provider<OrderProcessor> provider; public ProvidedOrderProcessor(Provider<OrderProcessor> provider) { this.provider = provider; } public void processOrder(Order order) { provider.get().processOrder(order); } }Now, we can get a order processor instead of the provider of it.
bind(Types.setOf(OrderProcessor.class)).toInstance(new HashSet<OrderProcessor>(){{ add(getLazyInstance(DbOrderProcessor.class)); add(getLazyInstance(MailOrderProcessor.class)); }}); OrderProcessor getLazyInstance(Class<? extends OrderProcessor> clazz) { return new ProvidedOrderProcessor(getProvider(clazz)); }Not as easy as we thought, right?
Bind one collection by multiple modules
What if we want to bind one instance of set using multiple module? There is a extension to Guice allow us to do that.public class Module1 extends AbstractModule { public void configure() { Multibinder<OrderProcessor> multibinder = Multibinder.newSetBinder(binder(), OrderProcessor.class); multibinder.addBinding().to(MailOrderProcessor.class); } } public class Module2 extends AbstractModule { public void configure() { Multibinder<OrderProcessor> multibinder = Multibinder.newSetBinder(binder(), OrderProcessor.class); multibinder.addBinding().to(DbOrderProcessor.class); } }Seems perfect? By the way, multibindings extension also support Map.
Limitation
But how about list? There is no official support to bind a list by multiple modules. Also, how to bind a chain of responsibilities (A.K.A decorators)?public class DecoratedOrderProcessor implements OrderProcessor { private final OrderProcessor decorated; public OrderProcessor(OrderProcessor decorated) { this.decorated = decorated; } public void processOrder(Order order) { try { decorated.processOrder(order); } finally { // do something; } } }When we have multiple decorators, which formed a chain of responsibilities, then the scenario becomes complex. If there is only one module, then we can use similar techniques like "ProvidedOrderProcessor" to bind it. But if there are more than one modules need to bind a element of the chain, then there is no official way to do it.
Use Guice to build extension point
Comparing Guice and Spring, one advantage I see is Guice promotes the modular design. By grouping functionality into modules, we can see plug and unplug some implementation based on the environment and requirement (for example, test and production). It is also possible in Spring, to be fair, but it is just easier and more often used in Guice world. Using Guice, we can define something as default, then allow other module to be plugged in and override it. Here is a list of techniques you can use to make this kind of effect:Choice 1: @ImplementedBy, @ProvidedBy
@ImplementedBy(MailOrderProcessor.class) public interface OrderProcessor { }Then, in case all modules did not specify the binding for OrderProcessor, then the default one (MailOrderProcessor in this case) will be used. If there is a binding bind(OrderProcessor.class).to(DbOrderProcessor.class), then that one will be used. This feature is really neat, mostly in the case when we need to change something in the unit test environment.
@ImplementedBy public interface CurrentTimeProvider { DateTime getNow(); public static class DefaultImpl implements CurrentTimeProvider { public DateTime getNow() { return new DateTime(); } } }In the production environment, the CurrentTimeProvider will automatically use the default implementation. But in the test, we can bind(CurrentTimeProvider.class).toInstance(new FixedTimeProvider(2008,5,12)); then we can write the test eaiser by fixing the time.
Choice 2: @Inject(optional = true)
public class ProcessOrderService { @Inject(Optional = true) OrderProcessor processor = new DummyOrderProcessor(); }When the provider side can not pick a default implementation, but the user side do know its default choice, then we can annotate the dependency as optional, and set a default value to it. When there is no binding to OrderProcessor, then the feature will be disabled by using DummyOrderProcessor. This behavior can be changed by plugging new module providing a implementatio of OrderProcessor.
Choice 3: Multibindings
The extension of Guice we've mentioned above allow us to bind a set or map by multiple modules. Using this extension, we can allow new module to plug in their new implementation to modify the system behavior. Very useful way to provide extension point.Choice 4: Module Override
This is a new feature of Guice 2.0. Easy to use, and "powerful".Module finalModule = Modules .override(new DefaultModule()) .with(new CustomizationModule());If CustomizationModule defines same key as DefaultModule, the one defined in DefaultModule will be overriden. It is useful in some case, but I don't think it is a good feature. Instead, if possible, we should split big module into smaller modules, and compose them depending on our needs, instead of override them from outside. But, Modules.override opened a way to allow multiple modules to bind same list, or even a decorator chain:
Key CUSTOMIZABLE_KEY = Key.get(OrderProcessor.class, new Before(MailOrderProcessor.class)); bind(Types.listOf(OrderProcessor.class)).toInstance(new ArrayList<OrderProcessor>(){{ add(new ProvidedOrderProcessor(getProvider(CUSTOMIZABLE_KEY)); add(getLazyInstance(MailOrderProcessor.class); }}); bind(CUSTOMIZABLE_KEY).toInstance(new DummyOrderProcessor());Before is a binding annotation. In another module, bind CUSTOMIZABLE_KEY again then we can override it:
bind(CUSTOMIZABLE_KEY).to(getLazyInstance(DbOrderProcessor.class));
Subscribe to Posts [Atom]