Elite Guice 3: Private Bindings

In the first and second parts of this series, I discussed hooks for extending Guice. In this post I introduce private bindings.

Don't emulate Guice

The key to a good extension is tight integration with Guice. In particular, the extension shouldn't copy functionality from Guice; doing so can lead to subtle differences between the extension and Guice itself. The assisted inject extension reimplements constructor injection and that causes problems:
  • Standard Guice can inject private constructors, but I forgot to do configure this in assistedinject.
  • AOP doesn't work for assistedinject-created objects

These differences aren't dealbreakers, but they do make the extension harder to use, and they violate the principal of least surprise. A better assistedinject would have used Guice's constructor injection.

The Injector is just a bunch of bindings

In the Multibindings extension, we secretly create a binding for each element in the set. These bindings are implementation details of the extension, but they need to be bound in the shared injector.

We have to somehow create the bindings without a chance of conflicting with user-supplied bindings. If Multibinder and the user both bound the same key, injector creation would fail.

Private Annotations to the rescue

The fix is to create a binding annotation type with a narrow scope, such as package scope. Other code won't have access to this annotation, and therefore we can be confident that only our extension can create bindings with this annotation.

In the Multibindings extension, there's a custom annotation called @Element that's secretly applied to all elements. Each annotation instance is given a unique ID to ensure that the hosting keys are distinct. Unless they're using a tool that interrogates the Injector, the users of Multibinder never see these bindings. They're created internally and used internally. But they allow all of Guice's features to work naturally on the bound elements.

Implementing the annotation type is code-intensive, but there's thorough instructions that describe how equals, hashCode and even toString() should be implemented.

This technique is also used in InterceptingInjectorBuilder, in order to let InjectionController work its magic.