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:
- Your code may not follow good design and often you should be able to refactor your code to avoid this.
- Buddy policy is Eclipse specific and OSGi does not know anything about this.
- 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.
Let me explain buddy policy using some of the valid use-cases (which I've encountered), where buddy policy comes into rescue:
- 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.
- 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.
- 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.
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.