Tuesday, March 18, 2008

Dependency Injection for Extensions

While preparing the tutorial on Spring Dynamic Modules for this years EclipseCon I played a bit with using Spring Dynamic Modules to dependency inject beans into views or other stuff I typically define as extensions for my Eclipse Rich Client Platform application. The problem is that the actual objects for those extensions are created by the Extension Registry and do therefore not pass the spring context for dependency injection. I needed to find a way to combine those two technologies somehow.

I came up with a very small and trivial solution for this: I created a class called SpringExtensionFactory that implements IExecutableExtensionFactory from the Extension Registry mechanism. This extension factory takes an ID from the extension definition, finds the spring application context for the bundle that contributes the extension and tries to retrieve the spring bean with the same id from that application context. This bean is then returned as the extension object. This trivial solution does not require any additional programming work to be done, no load-time weaving (for @Configurable, for example), just plain nothing.

Yon can download this SpringExtensionFactory from here, its open-source under EPL 1.0:
The interesting question is which ID this extension factory takes from the extension definition to look up the bean in the application context. The answer is: It is a two-step process. It first tries to find an "id" attribute within the configuration element of the extension definition. This is typically the case for RCP views, for example. They have an "id" attribute to identify the view. If there is no "id" attribute found within the configuration element it steps back to the extension definition itself and takes the "id" attribute from there.

If you have any questions I am happy to help you with this stuff. Just leave a comment (to share your thoughts and questions with the community).

10 comments:

Steve said...

Can you please post the source?

Thanks!
Steve

Steve said...

Nevermind....I just found the source jar inside the original jar :-)

Thanks!!!
Steve

Age said...

Hello Martin

I've got spring DM 1.1 M2 working in my RCP application but when I try to use your extensionfactory to create and inject one of my views, I run into some kind of race condition.

The problem is that somehow the view gets created before the spring DM extender has created an application context for the bundle.

When the factory tries to get a service reference for the bundl application context, it returns null but if I put a breakpoint in the extension factory (at the start of setInitializationData) and then resume the VM, the factory always finds the bundle application context.

Any idea how to stop this from happening ?

Age

Steve said...

In the included source, I see that the SpringExtensionFactory obtains the Spring application context through a BundleContext.getService call.

Martin, please correct me if I'm wrong, but won't using a ServiceTracker eliminate the race condition. For reference, an example of using ServiceTracker to obtain a Spring ApplicationContext can be found in the "Agile RCP" presentation from EclipseCon 2008.

Code: http://tinyurl.com/53uncv
PDF: http://tinyurl.com/3gtxk4

Hope this helps!
Steve

Martin Lippert said...

There are two points here:

- First, you need to make sure that the spring app context is created before the extension object is used. Therefore the bundle containing the extension needs to be activated. This is one thing I need to add to the spring extension factory (automatic activation if the factory is called to create the extension). Otherwise the application context will not be available and you will get the null reference or wait forever.
- The second thing you need to do with the latest spring dm versions is to tell sprind dm to synchronously create the application context for the bundle (via the appropriate spring manifest header: ) OR use the service tracker as Heiko does in the Agile RCP example (mentioned in the comment above). I think Heiko chose the better way to deal with the asynchronous creation of the application context and I should adopt that mechanism. Good hint!!! Thanks for that!!!

Age said...

OK, I'm sure Im missing something here.

I have configured both the spring dm extender bundle and your bundle to automatically start in my application's config.ini.

Then I have two bundles. One of them exposes a service through DM and the second one consumes it, also through DM. The second bundle also declares a spring bean for my view, using the view id as the bean id. In the same bundle's plugin.xml I declare the view and I use org.eclipse.springframework.util.SpringExtensionFactory as the view class. Also in the same bundle I added the following to the manifest:

Spring-Context: *;create-asynchronously=false

The view should be visible after application startup so it also gets created initially.

With all of this I get the reported null pointer when eclipse tries to instantiate the view.

So how do I make sure the Spring application context in the second bundle gets loaded before its views get created ? Should I try to track the application context service on bundle startup ?

I'm pretty new to all this so all help is greatly appreciated.

Thanks,
Age

Martin Lippert said...

In your line "Spring-Context: *;create-asynchronously=false" there is a missing ":" in front of the "=". It should be: "Spring-Context: *;create-asynchronously:=false" (this is a typo in the reference documentation of spring dm). You should definitely try this!!!

The second thing is that my first implementation of the spring extension factory assumes that the bundle contributing the extension is already active when the extension is created. This was just plain stupid and I will try to publish an improved version without that limitation soon.

HTH,
Martin

Age said...

Thanks for spotting the typo Martin. I did indeed copy that line straight from the Spring DM reference. With the correct line and your bundle configured to be activated on container startup, everything works.

I'm definitely interested in an improved version of your util bundle. My OSGI knowledge is still to basic to think of a solution other than externally forcing the bundle to activate.

Martin Lippert said...

I published an improved version of the spring extension factory that should make it a lot easier for you. You no longer need to take care of bundle activation yourself, for example.

Try it!

Age said...

Try it with the following URL:

http://www.martinlippert.org/download/springextensionfactory_1.0.1.zip