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


Problems and solutions

The biggest hurdle I tackled in the process of developing One-JAR was how to load JAR files that are contained inside another JAR file. The Java classloader sun.misc.Launcher$AppClassLoader, which takes over at the start of java -jar, only knows how to do two things:

• Load classes/resources that appear at the root of a JAR file.

• Load classes/resources that are in codebases pointed to by the META-INF/MANIFEST.MF Class-Path attribute.

Moreover, it deliberately ignores any environment variable settings for CLASSPATH or the command-line argument -cp that you supply. And it does not know how to load classes or resources from a JAR file that is contained inside another JAR file.

Clearly, I would need to get around this to meet my goals for One-JAR.

Solution 1: Expand supporting JAR files
My first attempt at creating a single executable JAR file was to do the obvious and expand the supporting JAR files inside the deliverable JAR file, which we'll call main.jar. Given an application class called com.main.Main, and assuming it depends on two classes -- com.a.A (inside a.jar) and com.b.B (inside b.jar) -- the One-JAR file would look like this:

main.jar

| com/main/Main.class
| com/a/A.class
| com/b/B.class
The fact that A.class originally came from a.jar has been lost, as has the original location of B.class. While this may appear to be a minor point, it can cause real problems, as I'll explain in a moment.

Expanding the supporting JAR files to the filesystem to create a flat structure can be quite time-consuming. It also requires working with build tools like Ant to expand and re-archive the supporting classes.

Aside from this minor annoyance, I quickly encountered two serious problems with expanding the supporting JAR files:

• If a.jar and b.jar contain a resource with the same pathname (say, log4j.properties ) which one do you pick?

• What do you do if the license for b.jar expressly requires you to redistribute it in an unmodified form. You can't expand it like this without violating the terms of that license.

I felt that these limitations warranted another approach.

One-JAR and FJEP
A recently-released tool called FJEP (FatJar Eclipse Plugin) supports the building of flattened JAR files directly inside Eclipse. One-JAR has been integrated with FatJar to support the embedding of JAR files without expanding them. See Resources to learn more about it.

Solution 2: The MANIFEST Class-Path
I decided to investigate a mechanism in the java -jar loader that will load classes which are specified inside a special file in the archive named META-INF/MANIFEST.MF. By specifying a property called Class-Path I hoped to be able to add other archives to the bootstrap classloader. Here is what such a One-JAR file would look like:


main.jar

| META-INF/MANIFEST.MF
| + Class-Path: lib/a.jar lib/b.jar
| com/main/Main.class
| lib/a.jar
| lib/b.jar
Did this work? Well it appeared to until I moved the main.jar file somewhere else and tried to run it. In order to assemble main.jar I had created a subdirectory named lib and pushed a.jar and b.jar down into it. Unfortunately, the application classloader was simply picking up the supporting JAR files from the file-system. It wasn't loading classes from the embedded JAR files.

To get around this, I tried using Class-Path with several variations on the rather arcane jar:!/ syntax (see "A note and a clue"), but I couldn't get anything to work. What I could do was deliver a.jar and b.jar separately and scatter them into the filesystem alongside main.jar; but that was exactly the kind of thing I wanted to avoid.

A note and a clue
URLClassloader is a base class of sun.misc.Launcher$AppClassLoader that supports a rather arcane URL syntax that lets you refer to resources inside a JAR file. The syntax works like so: jar:file:/fullpath/main.jar!/a.resource.

In theory, to get to an entry inside a JAR file inside a JAR file you would have to use something like jar:file:/fullpath/main.jar!/lib/a.jar!/a.resource, but unfortunately this doesn't work. The JAR file protocol handler treats only the last "!/" separator as indicating a JAR file.

But this syntax does hold a clue to my final One-JAR solution ...

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