I’ve been working on a little Java web framework [1] for an exploratory work project. I am building the framework and a sample app as a set of OSGi bundles to drastically reduce API surface area between components [2]. This also makes it easy to run my sample app directly within base Eclipse, using Eclipse’s built-in support for OSGi framework launches and a bundle-ized version of Jetty.
This configuration raises an interesting problem though, how do you inject the application code into the framework, since the framework obviously can’t statically depend on the application code, and OSGi will only let Java classes “see each other” [3] if one bundle makes a package (or packages) visible via an Export-Package
manifest declaration (e.g. Export-Package: my.api.package
) and another bundle declares an explicit dependency on that package via an Import-Package declaration (e.g. Import-Package: my.api.package
)? In other words, how will you avoid hitting java.lang.NoClassDefFoundError
s when trying to load the application code via reflection?
I sure didn’t know. Luckily I have a good buddy here at IBM in Research Triangle Park named Simon Archer who is an OSGi and Equinox expert [4], so I ran the problem by him. He told me about an OSGi manifest declaration I had never heard of called DynamicImport-Package
. My assumption that you can only get at code via explicit Import-Package
declarations was actually wrong.
Simon explained that the way DynamicImport-Package
works is that it basically allows a bundle to say “I want to be able to access any class that is part of an exported package in the runtime environment. So let’s say I have two bundles: bill.framework
and bill.sampleapp
. I want the code in bill.sampleapp
to run inside the web framework implemented in bill.framework
, but I obviously don’t want the bill.framework
code to have a static (class-level) dependency on the bill.sampleapp
code since the whole reason I’ve designed it as a framework is to allow build-time composition of arbitrary applications built on the framework [5]. So I put the following in bill.framework
‘s MANIFEST.MF
file:
DynamicImport-Package: *
Then in my Sample App bundle’s MANIFEST.MF
file, I put my application class in a package [6] that I export to the OSGi environment:
Export-Package: bill.sampleapp.app
Now the framework is able to dynamically load the sample app via reflection:
// MyFramework.java
String appClassName = System.getProperty("bill.app.classname");
IApplication app = (IApplication)Class.forName(appClassName).newInstance();
Voilà !
Footnotes:
[1] I know, because what the world needs now is another Java web framework. But as I observed in a journal entry, every framework is evil, except mine.
[2] Note that the framework itself doesn’t use or depend on OSGi. I build the bundles into a set of simple JARs that can run as part of a JEE web app or as standalone Java application again using an embedded Jetty web server.
[3] For a great primer on building modular Java applications with OSGi, see the recent book “OSGi and Equinox: Creating Highly Modular Java Systems” by McAffer, VanderLei, and Archer.
[4] E.g. Simon co-wrote the book mentioned in [3]. He is “Archer” 🙂
[5] Yes, I know. Most people call this pattern “dependency injection”. For the full treatise, see Fowler.
[6] The fact that you have to export the package for the code that you want to dynamically load wasn’t immediate obvious and Simon and I spent approximately twenty minutes staring at the screen wondering why we were getting java.lang.NoClassDefFoundError
even though we were using DynamicImport-Package: *
. After some unfruitful Googling, we decided to check out some bundle details using the OSGi console in Eclipse. As we were looking at the details for the sample app, I got the at the time unintuitive idea to try exporting the Sample App package. Sure enough this fixed it. Simon and I had a bit of a debate about whether or not it made sense to have to export application code since this effectively declares the application code to be API, which seems wrong – i.e. typically an application sits on top of a stack of code and depends on lots of stuff, but nothing depends on it.
But eventually we came to a reason that makes perfect sense for exporting the application code: If you didn’t have to explicitly export the code, theoretically any OSGi bundle’s code could get access to any other bundle’s code simply by declaring DynamicImport-Package: *
and loading random classes via reflection, defeating the whole purpose of the OSGi modularity system. So by requiring that the to-be-dynamically-loaded class be available to the environment via an explicit Export-Package
declaration you are still playing by the “normal rules” and just using reflection rather than static instantiation to poof up objects.
Of course this means that you should minimize your API surface area for the application class, so I put mine in its own package and its only public methods are those from the framework interface that it implements.
Good fences FTW!