Helping ordinary people create extraordinary websites!
GET OUR NEWSLETTER
Your Email:
 

Simplify Your Application Delivery with One-JAR

By P. Simon Tuffs
2005-04-22


Enter the JarClassLoader

At this point I was getting frustrated. How could I make an application load its classes from a lib directory inside its own JAR file? I decided that I would have to create a custom classloader to do the heavy lifting. Writing custom classloaders isn't a task to be undertaken lightly. While they're not really that complex, a classloader has such a profound impact on the application it controls that it becomes difficult to diagnose and interpret failures when they occur. While a complete treatment of classloading is beyond the scope of this article (see Resources), I will go over some basic concepts to ensure that you get the most from the following discussion.

Loading a class
When a JVM comes across an object whose class isn't known, it invokes a classloader. The job of the classloader is to locate the bytecodes for the class (based on its name) and then hand those bytes over to the JVM, which links them into the rest of the system and makes the new class available to the running code. The crucial class in the JDK is java.lang.Classloader, and the loadClass method which is outlined here:

public abstract class ClassLoader {

...
protected synchronized Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {...}
}
The main entry point to the ClassLoader class is the loadClass() method. You will note that ClassLoader is an abstract class but it doesn't declare any abstract methods, which leaves you without a clue that loadClass() is the method to focus on. In fact, it isn't the main method to focus on: back in the good old days of JDK 1.1 classloaders, loadClass() was the only place you could effectively extend a classloader, but since JDK 1.2 it is best left alone to do what it already does, which is the following:

• Checks to see if the class is already loaded.
• Checks to see if a parent classloader can load it.
• Calls findClass(String name) to let a derived classloader load the class.

The implementation of ClassLoader.findClass() is to throw a new ClassNotFoundException, and is the first method to focus on when implementing a custom classloader.

When is a JAR file not a JAR file?
In order to be able to load the classes inside a JAR file inside a JAR file (the crucial issue, as you'll recall), I first had to be able to open and read the top-level JAR file (main.jar above). It turned out that because I was using the java -jar mechanism, the first (and only) element on the java.class.path system property was the full path name of the One-JAR file! You can get to it as follows:


jarName = System.getProperty("java.class.path");
My next step was to iterate over all my application's JAR file entries and load them into memory, as shown in Listing 1:

Listing 1. Iterating to find embedded JAR files

JarFile jarFile = new JarFile(jarName);

Enumeration enum = jarFile.entries();
while (enum.hasMoreElements()) {
JarEntry entry = (JarEntry)enum.nextElement();
if (entry.isDirectory()) continue;
String jar = entry.getName();
if (jar.startsWith(LIB_PREFIX) || jar.startsWith(MAIN_PREFIX)) {
// Load it!
InputStream is = jarFile.getInputStream(entry);
if (is == null)
throw new IOException("Unable to load resource /" + jar + " using " + this);
loadByteCode(is, jar);
...
Note that LIB_PREFIX evaluates to the string lib/ and MAIN_PREFIX evaluates to the string main/. I wanted to load the bytecodes for anything starting with either lib/ or main/ into memory for use by the classloader and ignore any other JAR file entries in the loop.

The main directory
I've talked about the role of the lib/ subdirectory, but what's this main/ directory for? In brief, the delegation mode for classloaders required that I put the main class com.main.Main into its own JAR file so that it would be able to locate the library classes (on which it depends). The new JAR file looked like so:

one-jar.jar

| META-INF/MANIFEST.MF
| main/main.jar
| lib/a.jar
| lib/b.jar
In Listing 1 above, the loadByteCode() method takes the stream from the JAR file entry and an entry name, loads the bytes for the entry into memory, and assigns it up to two names depending on whether the entry represents a class or a resource. The best way to illustrate this is by example. Suppose a.jar contains a class A.class , and a resource A.resource. The One-JAR classloader builds the following Map structure named JarClassLoader.byteCode with a single key-value pair for classes, and two keys for a resource.

Figure 1. The in-memory structures of One-JAR


If you stare long enough at Figure 1 you can see that class entries are keyed based on their classnames, and resources are keyed on a pair of names: a global name and a local name. This mechanism is used to resolve resource-name conflicts: if two library JAR files define a resource with the same global name, the local names will be used based on the stack-frame of the caller. See Resources for further details.

Finding the classes
Recall that I left off in my overview of classloading at the findClass() method. Method findClass() takes the name of a class as a String and must locate and define the bytecodes that that name represents. Since loadByteCode kindly builds a Map between a class name and its bytecode, implementing this is now very straightforward: simply look up the bytecodes based on class name, and call defineClass(), as shown in Listing 2:

Listing 2. An outline for findClass()

protected Class findClass(String name) throws ClassNotFoundException {

ByteCode bytecode = (ByteCode)JarClassLoader.byteCode.get(name);
if (bytecode != null) {
...
byte bytes[] = bytecode.bytes;
return defineClass(name, bytes, pd);
}
throw new ClassNotFoundException(name);
}


Tutorial Pages:
» Power Programming with Custom Classloaders
» Overview of One-JAR
» Problems and solutions
» Enter the JarClassLoader
» Loading Resources
» Bootstrapping the JarClassLoader
» In Conclusion
» Resources


First published by IBM DeveloperWorks


 | Bookmark
Related Tutorials:
» All about JAXP, Part 1
» Make Database Queries Without the Database
» Load List Values for Improved Efficiency
» 2 Ways To Implement Session Tracking
» A Simple Way to Read an XML File in Java
» Develop Aspect-Oriented Java Applications with Eclipse and AJDT

Advertise with Us!


Tutorials Scripts Web Hosting Developer Manuals
Resources