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)And exactly the same for the "no scope" annotation:
@Target(ElementType.TYPE)
@ScopeAnnotation
public @interface HookedSingleton
{
}
@Retention(RetentionPolicy.RUNTIME)Unfortunately I couldn't find better names for these annotations:-(
@Target(ElementType.TYPE)
@ScopeAnnotation
public @interface HookedDefaultScope
{
}
Now let's see the implementation of the class implementing Scope:
public class HookedScope implements ScopeThis class has to be instantiated once for every scope used in the application (you want to hook ALL objects instantiation, don't you?)
{
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 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
Hooks just have to implement the CreationHook interface below:
public interface CreationHookNow the next step is to actually instantiate our HookedScopes and bind them to their
{
public <T> void process(T instance);
}
respective annotations, this is done in a Guice Module:
public class ScopeModule extends AbstractModuleAnd you pass this module when you create the Guice Injector:
{
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);
}
}
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 = ...;As you can see, this leads to a new problem: you will have to add your hooks to each individual
ScopeModule.SINGLETON.addHook(hook);
ScopeModule.NO_SCOPE.addHook(hook);
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 GuiceHookHelperNow this class centralizes all HookedScopes and all hooks, hiding all internals from the end user.
{
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;
}
}
}
}
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!
Very clever! Bob's got ambitious plans for construction interception but I think we'll have to leave that until after 2.0 to meet our self-imposed deadline.
ReplyDeleteCurious - could you expand on some of your use cases for this?
Also - you should look at the Commands API, which you could use to rewrite modules to include these hooked scopes.
Thank you for your comment Jesse.
ReplyDeleteMy use case is more or less explained in my post. Here are some more details:
I am currently working on "Guice-GUI" a GUI framework that, among other things, integrates Guice with Swing Application Framework.
In this framework, I define the main class that creates the Injector (and uses it later on to instantiate dialogs or other panels).
In the GUI, it is quite common to use the EventBus library; it has support for annotations. Often, some panels will use these annotations to subscribe to some EventBus topics.
My idea is then to give a chance to developers who use my framework to add one hook that will process EventBus annotations. This is just what I have done in one sample GUI I'll deliver with this framework.
For the Commands API, I prefer to wait until Guice 2.0 is released (or any beta is OK, as long as I don't have to build it myself, I've no time for it)