Inside Guice 2: exercising elements

Part 3 in a Series.

Background: modelling configuration

One of the most distinct features of Guice is our fluent embedded domain specific language (DSL). By chaining method calls, Guice supports a wide combination of binding sources, targets and scopes:

bind(new TypeLiteral<Connector<Processor>>() {})
.toProvider(new CheckoutProcessorConnectorProvider());

.toInstance(new InMemoryTransactionLog());

In our first implementation of the DSL, calls to bind() etc. configured the injector directly. This works well, but it's not very flexible. As long as creating injectors is all that we do with modules, it's sufficient.

The Elements SPI

In Guice 2, the DSL is used to build an intermediate configuration model. Calls to bind() etc. create Element instances. For example, the three statements above will create a LinkedKeyBinding, a ProviderInstanceBinding and an InstanceBinding. The API to get the configuration model from a module is called Elements.getElements().

By converting the method calls into value objects, we get an opportunity to inspect and transform them. For example, the following prints all of the keys bound in a module:
  public static Set<Key<?>> getBoundKeys(Module module) {
Set<Key<?>> result = newHashSet();
for (Element element : Elements.getElements(module)) {
if (element instanceof Binding) {
result.add(((Binding) element).getKey());
return result;

Internally, we use this API to simplify injector creation. Now we can process elements in order we want, even when that's different from the order that the user has specified. We prefer to handle scope registrations before bindings, and bindings before injection requests.

These elements are useful in other APIs. For module overrides, we take elements from two modules and compute a union. We can also use elements to unit test modules without having their dependencies on hand. They'll also come in handy for development tools.

By converting method calls into value objects, we can do powerful new things.