Wednesday, May 28, 2008

Dependency Injection for Extensions, Second Edition

At this years EclipseCon I implemented an extension factory utility that could be used to delegate the creation of an extension object to a spring context (if you are using Spring Dynamic Modules). The result was that you were able to define your extensions as spring beans including all the nice spring features (dependency injection, aop, dessert topping, whatever...). That first implementation was nice, but had a number of stupid limitations (worked only for active bundles, for example). The good news is: A new version is available that includes some nice improvements... :-)

Firstofall, here is the new version of the Spring-Extension-Factory:

The new features are:
  • Ability to declare the bean-id explicitly within the extension definition

  • Automatic start of non-active bundles (and application context creation via Spring Dynamic Modules) when an extension is being created

How can I use this extension factory? Lets look at some examples. Lets assume you implement an RCP application and you would like to define a view as spring bean to inject some dependencies. First, you declare your spring bean inside the spring context:
<bean id="myview"
class="org.eclipse.example.springdm.rcpview.View"
scope="prototype">
<property name="myService" ref="serviceBean"/>
</bean>


Okay, this defines the spring bean called "myview" and injects a bean with the id "serviceBean" as a dependency into my view. The class of the view is the normal view implementation that you are familiar with. Don't forget to declare the bean as of scope "prototype". Otherwise spring would return always the same object every time an extension should be created (which is not the contract of the extension mechansim).

Now we define the extension in the plugin.xml file:
<extension point="org.eclipse.ui.views">
<view name="Message"
allowmultiple="true"
icon="icons/sample2.gif"
class="org.eclipse.springframework.
util.SpringExtensionFactory:myview
"
id="org.eclipse.example.springdm.rcpview.view">
</view>
</extension>


The class that is now referenced in the extension instead of the view itself is the spring extension factory followed by a colon and the id of our bean from the spring context. The rest of the extension definition looks exactly as before (without the extension factory).

The Spring Extension Factory includes two additional options to refer to the bean id within the extension definition. This is the strategy that I implemented into the extension factory:

  1. If a bean-id is explicitly defined (like in the example above), use this id to lookup the bean.

  2. If not, search for an id attribute within the contribution element of the extension and use it as the bean-id.

  3. If no id attribute is found inside the contribution element, try to use the id of the extension itself as bean-id.


The first option is the most explicit one. The second option works just for those extension definitions where the corresponding extension point schema defines an id attribute as part of the specific extension (this is the case for views, but obviously not for all extension points). The third option can be used for all extension points but is less explicit that the first one. In the end its a matter of taste which option you prefer.

Comments, feedback and improvements, of course, highly welcome.

Have fun with it!!!

29 comments:

djo.mos said...

Marveloous ! This is really useful and smartly integrated.

Thanks :-)

Martin Lippert said...

Thanks for the feedback. Great to hear!

Heiko Seeberger said...

Martin,

As I can see you are using the Agile RCP ApplicationContextTracker. That makes me happy ;-)

Your auto-activation strategy is really cool and a great imporvement. Would you like to contribute your code to Agile RCP?

Heiko

Martin Lippert said...

Hi Heiko,

sorry that I forgot to mention you in the announcement. I indeed borrowed the application context tracker from agile-rcp but changed the retrieval of the service from "getService" to "waitFor(timeout)". This allows me to automatically start the bundle and wait for the application context to be created since this could happen asynchronously.

Martin Lippert said...

Oh, and feel free to borrow back the auto-activation strategy to your agile-rcp project. I contribute whatever you like, but I currently haven't the time to do it myself... Is that okay?

Cheers,
-Martin

Benedikt Arnold said...

Hi!
That sounds great. I will use your solution in Agile RCP.

Greetings,
Benedikt

Heiko Seeberger said...

Hi Martin,

No need to be sorry!!
I am happy to see someone using my stuff, that is great.

As I can see, Ben already is hungry to re-borrow ;-)

Cheers
Heiko

jawher said...

Hi Martin,
Thanks again for this smart plugin.

I'h having a problem though :(

When I try to open a Spring managed view (as in the example you show), I get the NCDFE :
java.lang.ClassNotFoundException: "org.eclipse.springframework.util.SpringExtensionFactory

I've tried importing the package "org.eclipse.springframework.util" or declaring your plugin as a required plugin, in both the plugin declaring the view and the plugin defining my application : same result.

Am I doing something wrong ?

Can you please elaborate more on how to use this plugin ?

jawher said...

Oops ... I've posted too quickly : I've mistyped the class name (a trailing " in the beginning).
Sorry, it works perfectly now.

Many thanks and cheers,
Jawher.

Martin Lippert said...

Great to hear that you got it to work for you! If you have any questions, feel free to ask.

Cheers,
-Martin

Unknown said...

Hello Martin,

I agree that your plugin is really efficient nevertheless I have unfotunatly faced a problem with the 1.0.1 version of spring framework extension utils.

In SpringExtensionFactory.java I had to comment the following part of a test "&& contributorBundle.getState() != Bundle.STARTING" to allow that my plugin could be launched in the lazy mode (ie "Eclipse-LazyStart: true" in the Manifest of my plugin).

If a plugin is launched in the lazy mode the bundle is considered as starting...

Do you have another solution to allow a plugin to be in lazy mode?

Regards,

Jimmy

derjan said...

Hi,

it works great. Do have any plans to contribute your work to the Spring DM project?
This is exacly one of the features I'm missing in Spring DM.

Thanks,
Jan

Martin Lippert said...

Hi Jimmy!

I tested this shortly and you are absolutely right. I will try to find a universal solution that works for both situations.

Stay tuned... :-)

Cheers,
-Martin

Martin Lippert said...

Hi Derjan,

no, there are currently no plans to contribute this to Spring DM, but if the Spring DM guys are interested in it, I would be happy to contribute. Why don't you submit a JIRA issue regarding this to the Spring-DM issue tracker? That would be nice.

Cheers,
-Martin

Martin Lippert said...

Jimmy, I fixed the problem by deleting the check for the starting mode. That works fine for bundles with or without the lazy start setting. I posted a new entry with the link to the download.

Thanks for your feedback!!!

Cheers,
-Martin

Unknown said...

Hi Martin,

That's also the solution I have chosen, but I hadn't tested if it was without any consequence.
Thanks for your work ;)

Jimmy

jawher said...

Martin,
I've opened a thread in the official Spring DM forum to discuss the possibility of integrating your (great) work into Spring DM:

http://forum.springframework.org/showthread.php?p=196381#post196381

cheers,
Jawher

Cody McCain said...

Martin,

I love this concept. I have downloaded your updated SpringExtensionFactory; however, I have been unable to successfully create executable extensions by replacing the class name with o.e.s.util.SpringExtensionFactory:beanName. Do I need to do anything other than:
1. Launch the osgi extender pluging
2. Have the bundle containing the extension depend on your plug-in
3.) Replace the "class" attribute in the extension declaration with SpringExtensionFactory:beanName

I get NullPointerExceptions when trying with RCP view parts and a ClassNotFoundException when I tried to use it with the IApplication.

If you could provide some guidance, I love to put together some documentation to help others get started.

Martin Lippert said...

Hi Cody,

would you send me your example as a small ZIP file? Then I could take a look at it. Would make it easier to find the problem, I think. My eMail address is: lippert@acm.org.

-Martin

tee said...

Hi Martin,

did you manage to solve Cody's problem at least? I am going both ways (LoadTimeWeaving and ExtensionFactory) and see which "wins" at the end.

In case of the ExtensionFactory i am facing the same problem than Cody,

Cheers,

Thomas E.-E.

Martin Lippert said...

Yes, I solved Cody's problem. Here is what I wrote him after looking at the example:

I looked through your example and I think I found the problem. Within your extension definition you define the ID of the view as "net.fractech.example.InjectedView". But your bean id is "injectedView" (inside the spring context). You can either define this bean id as the view id or (if you would like to keep the view id different from your bean id) you can define the bean id explicitly after the spring extension factory class:

class="org.eclipse.springframework.util.SpringExtensionFactory:injectedView"

HTH,
Martin

Emilio said...

Hey Martin,

I did what you said and everything is working now! Thank you very much.

Now I'll try to embed some EJB3 container as OSGI service and use all those things to develop a RCP application that can be extended to service orientation easily.

Thank you for your support.

Martin Lippert said...

Great to hear that you got it working!!!

Cheers,
-Martin

Colm Caffrey said...

Hi Martin. This is really great. I'm just wondering, would it be possible to also create the individual UI controls (text fields, buttons etc) in some way using the spring configuration? Or does the fact that you need the parent composite to create these controls make it not possible

Martin Lippert said...

Hi Colm!

You could use some hacky tricks to create the UI controls via the spring context, since Spring supports constructor injection. The only challenge is to get the parent component into the spring context as a bean. This should be somehow possible by using a factory bean definition inside the context that is fed with the parent component from outside, but this is a really hacky workaround. But why would you like to create the UI components via Spring? Isn't it enough to create the view or editor via Spring?

-Martin

Setya said...

Hi,

When I set class attribute to org.eclipse.springframework.util.SpringExtensionFactory the Eclipse Plugin Editor always shows a warning sign saying that the class does not exist in the plugin's classpath. I've added org.eclipse.springframework.util in Imported Packages.

Regards,

Setya

Martin Lippert said...

Hi Setya,

the problem was observed my other users of the extension factory as well. Ralf Ebert solved this issue here (take a look at one of the latest comments):

https://www.blogger.com/comment.g?blogID=18490491&postID=5155479122247291103

HTH,
-Martin

lordmaciek said...

thx Martin! you are the man!

amity said...

Hi all,

I am a newbee to Eclipse RCP and Spring. Could anyone help with link to an example project for this?

Thanks in advance.