Friday, September 14, 2007

Injecting method interceptors in Guice 1.0

Guice 1.1 will probably have injected method interceptors, but until then those of us who need this kind of thing will have to roll our own.

[Update 2009-1-3] Guice 2 has (or will have, depending on how you view its release status) a requestInjection facility.

The way I've been doing it is similar to the way Stuart McCulloch suggested, but more automatic: I extend AbstractModule by
  1. adding a registerForInjection method that takes a varargs array of objects that should be injected and
  2. overriding bindInterceptor to call registerForInjection(interceptors) before calling the superclass implementation.
The implementation of registerForInjection adds the elements of the array argument to a set of objects to be injected. There is a private method, injectRegisteredObjects, marked with @Inject; all it does is call Injector.injectMembers on every element of this set.

To ensure that injectRegisteredObjects is called, I bind the module class to the module instance. Since I want to be able to do this for every instance of the module, I bind with a unique annotation instance each time. The private method ensureSelfInjection does this binding once only for each module instance, so I can safely call it in registerForInjection.

Here's the code:

/**
 * An extension of AbstractModule that provides
* support for member injection of instances
* constructed at bind-time; in particular,
* itself and MethodInterceptors.
*/
public abstract class ExtendedModule extends AbstractModule {

/**
* Overridden version of bindInterceptor that,
* in addition to the standard behavior,
* arranges for field and method injection of
* each MethodInterceptor in {@code interceptors}.
*/
@Override public void bindInterceptor(Matcher<? super Class<?>> classMatcher,
Matcher<? super Method> methodMatcher,
MethodInterceptor... interceptors) {
registerForInjection(interceptors);
super.bindInterceptor(classMatcher, methodMatcher, interceptors);
}

/**
* Arranges for this module and each of the given
* objects (if any) to be field and method injected
* when the Injector is created. It is safe to call
* this method more than once, and it is safe
* to call it more than once on the same object(s).
*/
protected <T> void registerForInjection(T... objects) {
ensureSelfInjection();
if (objects != null) {
for (T object : objects) {
if (object != null) toBeInjected.add(object);
}
}
}

@Inject private void injectRegisteredObjects(Injector injector) {
for (Object injectee : toBeInjected) {
injector.injectMembers(injectee);
}
}

private void ensureSelfInjection() {
if (!selfInjectionInitialized) {
bind(ExtendedModule.class)
.annotatedWith(getUniqueAnnotation())
.toInstance(this);
selfInjectionInitialized = true;
}
}

private final Set<Object> toBeInjected = new HashSet<Object>();

private boolean selfInjectionInitialized = false;

/**
* Hack to ensure unique Keys for binding different
* instances of ExtendedModule. The prefix is chosen
* to reduce the chances of a conflict with some other
* use of @Named. A better solution would be to invent
* an Annotation for just this purpose.
*/
private static Annotation getUniqueAnnotation() {
return named("ExtendedModule-" + count.incrementAndGet());
}

private static final AtomicInteger count = new AtomicInteger();
}

There's a follow-up posting on DWR-Guice applications.

6 comments:

uwe said...

Cool. I came up with a slightly less complete version. Good to see, that this problem was successfully tackled before.

The idea with that Named-Annotation is great, i missed something like that.

thanks for posting.

Unknown said...

Dumb question -

return named("ExtendedModule-" + count.incrementAndGet());

what is named ? where is it defined?

Tembrel said...

Standard Guice 1.0:

import static com.google.inject.name.Names.named;

I've used a more encapsulated technique in the latest DWR code. See the guice.util package, and look at Numbered.java, NumberedImpl.java, and Numbers.java. Then, in AbstractModule.java, look at the ensureSelfInjection method, to see how it's used.

Unknown said...

aha! thanks for the quick reply Tim, will check those out :)

swankjesse said...

Sweet. FYI, Guice 2 makes this a bit easier with its requestInjection() method.

Unknown said...

Hi Tim,

a great post - it helped a lot. For a tutorial on Guice interceptors in general I chose a slightly different aproach by simply returning a list of injectees from the module.

You can find it here: http://musingsofaprogrammingaddict.blogspot.com/2009/01/guice-tutorial-part-2-method.html

I'd be happy on any comments,

Gunnar