In this first post, I'll talk about some of my experiments with
Google Guice Dependency Injection library.
I am using Guice 1.0 although 2.0 is planned for release this summer (but I prefer to wait and see...) Anyway, the problem I'm going to talk about seems not to be addressed in the future Guice 2.0 either (although the solution might be simpler later in 2.0, we'll see).
Guice is a very good library for Dependency Injection, it is heavily depending on Java 5 Generics thus ensuring very high confidence in your code robustness at compile-time. It is also using annotations a lot.
Guice API is quite simple for the end user, however it may be considered
too simple, considering a few missing features that look fundamental.
As an example, I have a use case where I need to process annotations (not Guice annotations, but other unrelated annotations) to any class instance created and injected by Guice. How can I do that? Guice 1.0 (and 2.0 as far as I understand) provides no "hook" system that would enable me to be notified whenever a new instance has been created and injected by Guice Injector. But that's exactly what one needs to be able to add the necessary annotation processing code for all new instances created by Guice!
So how can we do that?
After several experiments, I think I have found one way, not perfect (I'll explain why later), but still acceptable, that works fine with Guice 1.0 without any hack.
I call this way "
Guice creation hooks through Scope interception". Wait a minute! Guice does not provide any way to intercept
Scope methods! Correct, in Guice,
Scopes are not first-class citizen and cannot be injected, intercepted... That seems quite natural however (just imagine a
Scope that can be scoped!).
So how do we intercept
Scope methods then? Well, there are not too many possibilities: you have to create your own
Scopes, and have them delegate to real
Scopes (like
Scopes.SINGLETON which is one of the few standard Scopes that Guice brings out of the box).
Creating a new
Scope means creating a class implementing the
Scope interface, and defining an Annotation for it. Since we are creating Scopes that wrap other Scopes, we'll have to define one new Annotation for each Scope we want to wrap (that's the most boring part).
In my example, I'll define only 2 annotations used to define 2 scopes that respectively wrap Singleton and Guice default scope (which has no specific name):
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ScopeAnnotation
public @interface HookedSingleton
{
}
And exactly the same for the "no scope" annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ScopeAnnotation
public @interface HookedDefaultScope
{
}
Unfortunately I couldn't find better names for these annotations:-(
Now let's see the implementation of the class implementing
Scope:
public class HookedScope implements Scope
{
private final Scope _wrappedScope;
private final List<CreationHook> _hooks = new ArrayList<CreationHook>();
public HookedScope(Scope scope)
{
_wrappedScope = scope;
}
public void addHook(CreationHook hook)
{
_hooks.add(hook);
}
public <T> Provider<T> scope(Key<T> key, final Provider<T> creator)
{
Provider<T> hookedCreator = new Provider<T>()
{
public T get()
{
// Actually create a new T instance
T instance = creator.get();
// Add processing to newly created instance
for (CreationHook hook: _hooks)
{
hook.process(instance);
}
return instance;
}
};
// Make sure that we are called back whenever an instance is actually
// created by unscoped
if (_wrappedScope != null)
{
return _wrappedScope.scope(key, hookedCreator);
}
else
{
return hookedCreator;
}
}
}
This class has to be instantiated once for every scope used in the application (you want to hook
ALL objects instantiation, don't you?)
This class works as follows:
First it stores the actual
Scope that it is wrapping.
Then, when its
scope() method is called, it is passed (
creator, 2nd argument) a special
Provider that is the
one Provider that unconditionnally instantiates a
T. Thus if we just decorate
creator, and pass it along to the real
Scope that we wrap, the decorated
Provider will be called every time a new creation is about to happen. Our decorated
Provider (named
hookedCreator) first simply calls the real Guice
creator, then it passes the newly created instance to any hook that is registered to it (through its
addHook method).
Hooks just have to implement the
CreationHook interface below:
public interface CreationHook
{
public <T> void process(T instance);
}
Now the next step is to actually instantiate our
HookedScopes and bind them to their
respective annotations, this is done in a Guice
Module:
public class ScopeModule extends AbstractModule
{
static HookedScope SINGLETON = new HookedScope(Scopes.SINGLETON);
static HookedScope NO_SCOPE = new HookedScope(null);
@Override protected void configure()
{
bindScope(HookedSingleton.class, SINGLETON);
bindScope(HookedDefaultScope.class, NO_SCOPE);
}
}
And you pass this module when you create the Guice Injector:
Injector injector = Guice.createInjector(new ScopeModule(), ...);
Note the static
SINGLETON and
NO_SCOPE defined above, they will be needed so that you can
add your hooks when you need:
CreationHook hook = ...;
ScopeModule.SINGLETON.addHook(hook);
ScopeModule.NO_SCOPE.addHook(hook);
As you can see, this leads to a new problem: you will have to add your hooks to each individual
HookedScope that you have created; in the example above that's only 2 scopes, but you may have more in your application!
Thus, a better way is to implement the whole logic of creating the
HookedScopes and adding hooks in one single helper class:
final public class GuiceHookHelper
{
static private final Map<Scope, Scope> _allScopes =
new HashMap<Scope, Scope>();
static private final List<CreationHook> _hooks =
new ArrayList<CreationHook>();
// Use the following improved scopes in bind()...in(scope) instead
// of Scoped.SINGLETON or no scope at all.
static final public Scope SINGLETON = hook(Scopes.SINGLETON);
static final public Scope NO_SCOPE = hook(null);
static public Scope hook(Scope wrappedScope)
{
Scope result = _allScopes.get(wrappedScope);
if (result == null)
{
result = new HookedScope(wrappedScope);
_allScopes.put(wrappedScope, result);
}
return result;
}
static public void addHook(CreationHook hook)
{
_hooks.add(hook);
}
static public void removeHook(CreationHook hook)
{
_hooks.remove(hook);
}
static private class HookedScope implements Scope
{
private final Scope _wrappedScope;
private HookedScope(Scope scope)
{
_wrappedScope = scope;
}
public <T> Provider<T> scope(Key<T> key, final Provider<T> creator)
{
Provider<T> hookedCreator = new Provider<T>()
{
public T get()
{
// Actually create a new T instance
T instance = creator.get();
// Add processing to newly created instance
for (CreationHook hook: _hooks)
{
hook.process(instance);
}
return instance;
}
};
// Make sure that we are called back whenever an instance is actually
// created by unscoped
if (_wrappedScope != null)
{
return _wrappedScope.scope(key, hookedCreator);
}
else
{
return hookedCreator;
}
}
}
}
Now this class centralizes all HookedScopes and all hooks, hiding all internals from the end user.
With this class, it is now easy to wrap other scopes and add hooks.
The main drawback of this approach is that you cannot use standard Guice scope annotations (
@Singleton or no annotation at all) for scoping your classes. You have to use new annotations, even for the default scope! Compared with what it brings, I think this drawback is not that big.
These classes will be part of my future
Guice-GUI framework (to be released this summer on SourceForge) where adding annotation processing to Guice instantiated classes makes sense when you want to use
EventBus with its annotations for instance (common in GUI development).
For the time being, you can find the complete source code for the experiments presented in this post, a maven pom.xml and eclipse project settings
here. Just unzip it and either import it into eclipse or install it with maven. The project includes a couple of unit tests (with TestNG).
Enjoy!