Thursday, September 17th, 2009
OSGi containers give developers a lot of power when it comes to packaging Java code. It helps to address some of the problems not (easily) otherwise solvable with the standard packaging mechanism provided by Java.
For example, I may need to have a public class that is available to my entire module. A class such that is not intended to be part of my public API. Call it a maintenance API, or internal API, or otherwise. The point is: It’s “public”, but not public.
Convention is typically used for such situations. My public API — the one intended for clients integrating with my system — might be exposed via the com.patrickschneider.api, while my maintenance/internal-only APIs might be exposed via something like com.patrickschneider.spi. Slap some Javadoc on top of this, make sure your SLA is up-to-date, and now you can tell your clients that they shouldn’t have been using those “spi” classes in the first place.
Fair enough. But wouldn’t it be nicer if you could avoid this in the first place? An OSGi container, can provide the measure of control, at runtime, to alleviate this problem. By using the ExportPackage manifest header, you retain explicit control over which packages will be available to other bundles / code. If it’s not exported, it won’t be seen.
This ability to hide and expose things so nicely also has some sneaky gotchas. The symptoms can range wildly; some of what I’ve personally seen are arcane errors including VerifyError and NoClassDefFoundError. Anytime something like this pops up, it seems that there is a ClassLoader to blame. And when dealing with OSGi, class loading is a huge deal.
Take for example the ability of an OSGi bundle to package within itself other jar files. These are often packaged inside of a “lib” directory inside of the bundle; this is arbitrary, though, and must be specified through the Bundle-ClassPath header in the bundle manifest. Crack open such a bundle, and you will see each jar in a comma-separated list. You’ll likely also see a reference to the root of the bundle, specified with by a full-stop (”.”).
The best piece of advice I can give regarding packaged dependencies: Do not export their contents.
Why would you export a dependency’s contents? Well, the thinking might go something like this… “I have Bundle A, and it packages CGLib. We are introducing Bundle B, and it needs to do some code generation of its own. No need to re-package the dependency; let’s just export what B needs from A.”
If your programming in a bubble, you might get away with this. Bundle B utilizes some CGLib code exported from Bundle A, and you’re on your merry way. But remember that Exports are available to the entire OSGi container — other bundles that use the packaged dependency may be affected by your Export choices. This is especially worrisome in the case of our example, where we’ve chosen to partially export the dependency. The class loading details get pretty hairy, so again, I’ll re-emphasize: Do not export packaged dependencies from an OSGi bundle.
