Friday, August 14, 2009

Dynamic Classpaths

The JVM's classpath is both one of the more confusing and annoying aspects of Java. Issues always tend to come up when you are trying to make your application easily deployable on client machines, as you inevitably have to jump through some hoops with proper placement of jars, and perhaps some nifty shell scripts to tell the JVM where those jars live.

Because of a combination of the aforementioned reasons and idle curiosity, I set out to see if I could run my application entirely from a single jar, which contained its required library jars within itself. In researching this idea, I found a neat way of dynamically adding jars to your JVM's classpath at run-time.

*N.B. While I consider the code that follows to be 'cool', I don't necessarily consider this to be 'best-practice' for setting classpaths. I prefer putting all of my library jars into a single folder (e.g. './lib'), and bringing the folder onto the classpath using
'-Djava.library.dirs=./lib'.

There are two interesting features of the JVM I discovered that I want to showcase: finding out where you (the code) live in the real world (the file system), and adding jars to the classpath at runtime.

Where do I live?

Similar to Ruby's (and other scripty languages') __FILE__, you can find the actual path to your executing code in Java, as long as you are allowed to by the security / permissions manager. This info is stored in the CodeSource object, which you can get to like thus:

URI clientJarUri = DynamicClasspath.class.getProtectionDomain().getCodeSource().
getLocation().toURI();

The javadoc on the getProtectionDomain() method states: If there is a security manager installed, this method first calls the security manager's checkPermission method with a RuntimePermission("getProtectionDomain") permission to ensure it's ok to get the ProtectionDomain, so keep that in mind when attempting this on secured environments.


Where does my code live?

Once you have the location of your code jar, you can use that to find jars which are relative to it, and tell the JVM about them using the system's URLClassLoader. Classloaders in the JVM are hierarchical, with child instances delegating class requests to their parent if they can't find what they are looking for. The premise here is that we can get at that root classloader, and add to its search path. At first, it seems that the appropriate method to use (addURL()) is hidden to us, but upon further Reflection (ha ha), we have what we need to proceed:

Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] {URL.class});
addURL.setAccessible(true);

Now, we can merrily add as many URL references to jars as we desire:

ClassLoader classLoader = ClassLoader.getSystemClassLoader();
for( URL jarUrl : classpathUrlsToAdd ){
addURL.invoke(classLoader, jarUrl);
}

Putting it all together

We now have a simple mechanism for finding out where we are, and adding additional classpath references to the JVM at run-time. This could prove to be useful in certain scenarios, such as deploying apps in a self-contained jar file, as I have done. My build process added all of my library jars at the root of the 'uber-jar', and in the main-class of that jar (my 'bootstrap' class):

  1. Find my location on the file system
  2. Extract all of the jar files from within the currently executing client jar
  3. Add all of those jars to the classloader dynamically
  4. Invoke the actual main class of my app

The only annoying bit was that the bootstrap class doesn't have access to any of my library jars, and therefore I wasn't able to use my favorite library (commons-io) for extracting the jar files from the client jar :-(.