Basically I thought I'd take a stab at trying to create a patch for
construction listeners. My initial idea was to add a method
postConstructionHook(InternalContext context, Object value) method to
InjectorImpl - then figure out later on how to register listeners to
the injector.
I've not really noodled the internals much so did a bit of
IDEA-walking through the code and started down the path of adding the
hook to all invocations of the InternalFactory; which certainly
catches all the points at which an object is constructed; though you
end up with duplicate events as many of these call sites are nested.
Being a total newbie at Guice implementation code - I had a quick look
in the debugger and didn't see any obvious solution to the right call
sites to include the event notification to catch all object
constructions without duplicates - so figured I'd shoot a mail to this
list to see if I was totally off base or if there was some other
cunning way to do it (maybe at the binding layer is cleaner?).
Any thoughts? If someone's working on it already I'll happily work on
something else :)
--
James
-------
http://macstrac.blogspot.com/
Open Source Integration
http://open.iona.com
OK no worries; I'll hold off a little while.
> What I am interested in is what the API
> will look like -- how do you setup the
> construction listeners in your module?
> And what does the interface for handling
> them look like?
BTW I wonder should we allow the post construction listener to return
a new instance (as it could return a proxy?) or should we restrict it
to being able to just perform some additional injection or calling of
lifecycle methods? The former might be more useful for optionally
applying interceptors and returning some kinda proxy.
How about something like this as a strawman API
/** invoked after Guice has constructed the object T */
public interface PostConstructionHandler<T> {
T postConstruct(T value);
}
(We could pass in some kinda context as a parameter to postConstruct()
to indicate where the value is being injected but am not sure how
useful that really is right now)
Then for example, to support Spring lifecycles we could write a class like this
public class SpringLifecyle implements
PostConstructionHandler<InitializingBean> {
public InitializingBean postConstruct(InitializingBean value) {
try {
value.afterPropertiesSet();
return value;
}
catch (Exception e) {
throw new PostConstructionRuntimeException(e);
}
}
}
Then to register the post construction handler we can use a similar
mechanism to the interceptor approach; so that we only cause the
overhead of the PostConstructHandler invocations when they are
actually used (on certain types or types with certain annotations and
so forth).
void bindPostConstructionHandler(Matcher<? super Class<?>> classMatcher,
PostConstructionHandler<?>... handlers);
Incidentally I did wonder about some kinda Module method a-la @Provides. e.g.
public class SpringModule extends AbstractModule {
...
@PostConstructHandler
public InitializingBean postConstruct(InitializingBean value) {
...
}
}
Though to be honest there's not gonna be that many implementations of
PostConstructionHandler<?> around the place so I don't think we need
to go too far in making it super easy to write them (only a few
frameworks will implement them typically). Whats most important I
think is avoiding their cost from most Guice users unless folks
actually use them - so we should encourage framework folks to use a
good Matcher to minimise PostConstructionHandler overhead.
After writing this mail I'm wondering if a more natural place to add
support for the PostConstructionHandler code is to mirror the
Interceptor code for this rather than using the InternalFactory code.
But that would prevent users from using any of the standard Guice
scopes right? So no SINGLETON/REQUEST/SESSION scopes? Or do you have a
cunning way of wrapping then under the covers?
Ah great idea! I guess the only downside with that approach is it
introduces another object construction (the ConstructorInvocation) for
each object being constructed that has a ConstructorInterceptor; plus
from an implementation perspective it might sometimes be a bit tricky
grabbing the Constructor reference (but thats less of an issue). On
the plus side though it'd certainly make the API more consistent with
the aopalliance APIs
It turned out - now you suggested the idea - it was super easy to implement! :)
I've a first spike of an implementation working; most of the code is
just boilerplate code; I just followed the way MethodInterceptors
worked and it all seemed super easy. Many thanks Dhanji!
The patch is attached here...
http://code.google.com/p/google-guice/issues/detail?id=78
I'll try tidy up the generics code in
ProxyFactory.wrapConstructorProxy() some more (my generics ninja was
letting me down) and add a few more unit tests, but so far its looking
good!
So you can now just do
bindConstructorInterceptor(matcher, interceptors);
just like with method interceptors and it seems to just work. e.g. see
the IntegrationTest.testConstructorInterceptor()
Many thanks!
BTW I've just attached an updated patch which includes some
ConstructorInterceptor implementations - one for Spring's
InitializingBean along with a jsr250 module with support for
@PostConstruct with some tests. Both interceptors come with a handy
static bind(Binder) method to make them real easy to bind without
needing to grok the right Matchers etc
Very cool! It looks quite simple.
Dhanji.