Saturday, March 6, 2010

Eclipse ClassLoading and Buddy Policy

Eclipse Class Loading:

One of the recent things we did on our project is to convert all our Eclipse plugin dependencies from jars on classpath to OSGi bundles. You can see why we had to do this and how we did this in my previous blog post. As you might know, in Eclipse, each plugin (or bundle) has it's own classloader. When a plugin needs to load a class, it will look in java.* package, or it it's own bundle, or from one of the import packages or from one of the required bundles or from one of the plugin's fragments or from packages imported using DynamicImport-Package. The search for class is done in the same order as specified here. If none of these work, then Eclipse will not be able to find the class.

You could think this is good enough and yes, this works for most cases. But, in some cases where third party libraries need access to your plugin/bundle, this would fail. This is because only your bundle has dependency to third party library and not the other way. So, this sounds like a circular reference right? This is where buddy policy comes to rescue. Before looking at buddy policy, let me say that you should not use buddy policy unless needed. This is because:
  1. Your code may not follow good design and often you should be able to refactor your code to avoid this.
  2. Buddy policy is Eclipse specific and OSGi does not know anything about this.
  3. Also, since this is not normal OSGi, BND bundle generator, does not support this and you have to manually customize your bundle manifest, by adding the buddy policy entry.
Buddy policy and it's use-cases:

Let me explain buddy policy using some of the valid use-cases (which I've encountered), where buddy policy comes into rescue:
  1. Using ORM frameworks like iBatis/Hibernate - Your database bundle would depend on iBatis/Hibernate and the other way is not true. This works in normal java projects since all jars share the same classpath and so iBatis/Hibernate can load your classes at runtime. But, in Eclipse/OSGi world, since iBatis/Hibernate does not know about your classes (since they are in a different bundle, which it cannot see) it cannot load any of your classes at run-time. So, the database bundle should have: Eclipse-RegisterBuddy: org.apache.ibatis.sqlmap in it's manifest and iBatis bundle should have Eclipse-BuddyPolicy:Registered in it's manifest. Here, iBatis bundle says that I'm registered for buddy policy and database bundle says I'm a buddy of iBatis. So, whenever iBatis cannot find a class or resource, before throwing ClassNotFoundException, bundle class loader will see if the iBatis bundle is registered for buddy policy. If yes, it will check who are the buddies of iBatis. If it finds any buddies, it will try to load the class from it's buddies. Note that buddy-policy search is not recursive i.e if a buddy does not have a requested class, it's buddies won't be searched recursively. In our case, since the database bundle is a buddy of iBatis, the classes are loaded from the database bundle. Similar case holds true for hibernate also.
  2. Using commons configuration - Say if your bundle uses commons configuration to load a properties file (for ex like, Configuration config = config = new PropertiesConfiguration("myproperties.properties");), your bundle would depend on Commons configuration, but the reverse is not true. But, commons configuration bundle needs access to myproperties.properties, which lives in your bundle. So, your bundle should have: Eclipse-RegisterBuddy: org.apache.commons.configuration in it's manifest and commons configuration bundle should have Eclipse-BuddyPolicy:Registered in it's manifest. This way commons configuration can access the properties file which resides in your bundle.
  3. Using XStream - Say if your bundle uses XStream to serialize the objects, your bundle would depend on XStream, but reverse is not true. But, XStream needs access to the classes in your bundle during deserialization process. So, your bundle should have: Eclipse-RegisterBuddy: com.thoughtworks.xstream in it's manifest and XStream bundle should have Eclipse-BuddyPolicy:Registered in it's manifest. This way XStream can instantiate the classes which resides in your bundle.
Buddy Policy Options:

Note that in the above cases, Eclipse-BuddyPolicy:Registered has been used. As explained above, "Registered" buddy policy only consults all the registered buddies to load the requested class. There are other buddy policy options, but I did not have to use them till now. "Registered" works for most cases and is very performant also, since the bundle has to search only it's buddies to load the requested class. Anyway, here are the other buddy policy options for reference:
  • dependent - All bundles which depends on this bundle will be searched to load the class. This option would also work in all of the above cases, but would be slightly less performant since the search had to be done in potentially more bundles.
  • global - Searches all the available packages which are exported. This also would work in our use cases above, but will be potentially even more less performant than dependent.
  • app, ext, boot - These are the other three options available and they use the app, ext and boot classloaders respectively to load the requested class.

5 comments:

Arunachalam said...

pretty informative. Thanks for sharing this.

Matthias said...

Hi Raja,

do you know if the class definition is requested from a buddy class loader only one time (so the first time) or if the bundle class loader is asked for the class definition each time an object of the class is created?

Example: Bundle X needs class A which buddy Bundle Y has loaded.

for (int i = 0; i < 10; ++i) {
A a = new A();
}

Is the class definition only loaded once for class A (and the class is stored) or does the bundle class loading takes places for every instantiation of class A!

Raja said...

Matthias - I'm not sure. I think it would be every time an object is created, but wouldn't be surprised if it remembers and uses the same one subsequent times. Can I know why you are asking this though? Are you concerned about performance?

Matthias said...

Hi Raja,

First thanks for your reply!

Yes I'm concerned about performance. Currently I use Buddy Class Loading because Hibernate (is in an own bundle) wasn't able to find my model classes which are contained in another bundle.

So if the Hibernate Bundle Class Loader would only use Buddy Class Loading one time for each model class then it would be acceptable. But if always the Buddy bundles are requested for every model class instantiation then I might get a performance problem.

Greetings,
Matthias

Kasim Sert said...

Hi Raja,
I am having a problem with my eclipse plugin, may be you may have a comment on this.
In my plugin i am parsing wsdl using wsdl4j api.but when i am doing this, getting Verification Error on implementation class methods.
For instance; lets say i have a interface a inside my method and its implementation object aImpl.when i call a.b() it gives verify error, however if i did ((aImpl)a).b() i can invoke this method.
As far as i can see there is a clasppath problem here.May be interface and its implementation is not loaded by same classloader and they do not know about each other.
Also another interesting point is although at runtime a.b() got verification error i can invoke it in eclipse debug display or by debug inspect(and its type is seen like aImpl).
I hope i managed to explain my case.Do you have any suggestion, or opinion about what could be the problem here ?
Thanks in advance.