Elite Guice 1: Initialize your extension
I recently wrote some fairly, um, extreme code for the Guice Mutibindings extension. That code makes use of several tricks that you might find useful in your own Guice extensions. In this series I'm going to show-and-tell the various clever code tricks from Multibinder.java.Initialize an extension
Guice doesn't have a plug-in API and it probably never will. But if it did, it would probably provide a mechanism for you to initialize your plugin:
public interface GuicePlugin {
void initialize(Injector injector) throws ConfigurationException;
}
This interface doesn't exist. But that's fine, because Guice does something better - it injects members into every bound instance and provider instance at injector-creation time. And where there is injection, there is initialization. There's two steps to getting Guice to call your initialize method:
- Bind an instance, or a provider instance.
Usually your extension is going to bind something anyway. Multibinder binds itsSet
, AssistedInject binds itsFactory
, ThrowingProviders binds itsThrowingProvider
. If you have absolutely nothing to bind, create a class and bind an instance of that.You must bind either an instance, a provider instance, or an eager singleton. These are the only things that get injected at injector-creation time.
- Create a
@Inject
initialize() method on the bound instance.
This method gets called at injector-creation time. This is that important stage after all modules have been executed but before the Injector has been returned to its creator. Your initialize method can take any injectable objects as parameters - usually the Injector is a useful choice, since it allows your extension to review its complete set of bindings.The method can also throw exceptions to indicate that it's improperly configured. Guice 1.0 has a bug where sometimes these exceptions don't halt injection. But recent snapshots have fixed this problem and you can now reliably use exceptions to fail if your extension is misconfigured. AssistedInject uses this feature to fail if your assisted constructor cannot be injected.
Example
In
Multibinder.java
, we bind a provider instance for the set: public void configure(Binder binder) {
...
binder.bind(setKey).toProvider(setProviderInstance);
}
That provider has our initialize() method. In this case, the initialize method loops through all of the injector's bindings, collecting providers for the elements of the collection:
/**
* Invoked by Guice at Injector-creation time to prepare providers for each
* element in this set. At this time the set's size is known, but its
* contents are only evaluated when get() is invoked.
*/
@Inject void initialize(Injector injector) {
providers = new ArrayList<Provider<T>>();
for (Map.Entry<Key, Binding> entry : injector.getBindings().entrySet()) {
if (keyMatches(entry.getKey())) {
Binding<T> binding = (Binding<T>) entry.getValue();
providers.add(binding.getProvider());
}
}
}
Hooray, our extension is initialized. In future posts I intend to discuss duplicate binding, private bindings, and perhaps annotation nesting.