com.mydomain.mypackage.Main, you can simply say:
java com.mydomain.mypackage.MainHowever, this assumes that some magic has been applied to your shell environment to set the value of your
CLASSPATHso that the java runtime can actually find this class. The
CLASSPATHalso has to contain all of the other classes which will be loaded and executed by your main program.
This is where things start to get much more complicated. Multiple entries on the
be separated by a special character, and this special character is unfortunately operating system
dependent (since it would otherwise interact badly with the common command shells). For example
on UNIX it is ':', on DOS it is ';'. If these choices were reversed, the command shell on each
operating system would become very confused. And there had better not be version any conflicts between
entries on the
CLASSPATH: the order of entries
controls the order of class resolution, incompatible versions created by incorrect orderings
will lead to obscure
NoSuchMethodException exceptions and other runtime problems.
Wouldn't it be nice if you could just wrap up all your classes into one big executable,
and run it? Well the Java2 Runtime Environment has a way of doing this using a JAR launcher.
If you bundle your class, and all the supporting classes in a file called myapp.jar, and
MANIFEST.MF appropriately, you can simply say:
java -jar myapp.jarVery nice. So what's the catch? There are numerous. One catch is that you must first unjar all of the supporting classes and flatten them out into a single directory tree before creating the final myapp.jar. For example: what happens if your application depends on some of the web-services JAR files from the Java Web Services Developer Pack? There are many of them and they all contain code which lives in the same packages (javax.*, com.sun.* etc).
Is this a problem? Well it may be. Consider that after you have expanded all of the
code into a single tree, you have lost all trace of any code signatures that might have been
present. Why? Because code signatures are located in
/META-INF/MANIFEST.MF file, and
each jar file will contain one of these manifest files, with different content. Likewise,
any resources which have the same name (a distinct possibility given that the jar files
themselves may contain flattened out packages) will be in conflict: you can only have one
log4j.properties to control logging, where you might like to enable/disable
logging on a boundary set by the individual jar files.
Wouldn't it be nice if you could just bundle the supporting jar-files into your
file without expanding them? This is the problem addressed, and solved, by One-JAR.
Class-Pathto solve this problem.
But first, lets set up a concrete example of what we're trying to do. Suppose our
main class, plus other classes we have written is in a JAR file called
Suppose that our application depends on two other JAR files, called
Can we construct a manifest which will allow the JAR launcher to run this? First, we construct
our compound Jar file:
$ jar -tf myapp.jar META-INF/MANIFEST.MF lib/a.jar lib/b.jar main/main.jarThe manifest contains a
Main-Classsetting to launch our application, and entries for the supporting JAR files
Manifest-Version: 1.0 Class-Path: main/main.jar lib/a.jar lib/b.jar Main-Class: com.main.MainWhat happens if you run this?
$ java -jar myapp.jar A() B()Looks good, exactly what we want right? Wrong. What's happening here is that the
Class-Pathentries are being resolved in the file-system, not inside the JAR file. If you move this JAR file somewhere else and try to run it, you get the following:
$ java -jar myapp.jar Exception in thread "main" java.lang.NoClassDefFoundError: com/main/MainThis is most unfortunate, and most frustrating, and pretty much unexpected by developers who follow this route.
What about using the 'jar:' protocol in the
Class-Path entry? This
doesn't work either. It appears that the JAR loader only supports file-based URLS. Which is where One-JAR
If you're really impatient to see One-JAR in action without writing any code or jarring any jars, follow this link.
At this point we have to augment our
myapp.jar file with some bootstrap
code, and a
META-INF/MANIFEST.MF file to let the bootstrap code assume
One-JAR ships with a pre-built bootstrap jar file called
one-jar-boot.jar that contains all the code you need
to do this. You can download the latest pre-built
$ jar -tf one-jar-boot.jar META-INF/MANIFEST.MF com/simontuffs/onejar/Boot.class com/simontuffs/onejar/Handler$1.class com/simontuffs/onejar/Handler.class com/simontuffs/onejar/JarClassLoader$ByteCode.class com/simontuffs/onejar/JarClassLoader.class boot-manifest.mfThere are five classes which control the bootstrap process, all residing in the
com.simontuffs.onejarpackage. We'll be discovering the details of these later.
There is also a pre-built manifest file called
boot-manifest.mf. To complete
the One-JAR deployment process, create a suitable directory and expand this file into it, then
myapp.jar file as follows:
$ mkdir boot $ cd boot $ jar -xvf ../one-jar-boot.jar $ jar -uvfm ../myapp.jar boot-manifest.mf .Thats it! You can now run your application in the simplest possible fashion:
$ java -jar myapp.jar A() B()
Well, One-JAR has some built-in diagnostics that you can enable to see what it is
doing on behalf of your application. These can be enabled using command-line switches
to the JVM, placed before the
INFOlevel diagnostics. These show high-level operations inside the One-JAR support code.
VERBOSElevel diagnostics. These show in tedious detail the operations inside One-JAR, including class-loading and resource resolution.
$ java -Done-jar.info -jar myapp.jar Boot: Info: using JarClassLoader JarClassLoader: Info: caching lib/a.jar JarClassLoader: Info: caching lib/b.jar JarClassLoader: Info: caching main/main.jar A() B()Now we see that something interesting is indeed happening inside the application: One-JAR is using a custom classloader called
JarClassLoaderto load information from the
myapp.jarfile. We can get more detail:
$ java -Done-jar.verbose -jar myapp.jar Boot: Info: using JarClassLoader JarClassLoader: Info: caching lib/a.jar JarClassLoader: Cached bytes for class com.a.A.class JarClassLoader: Info: caching lib/b.jar JarClassLoader: Cached bytes for class com.b.B.class JarClassLoader: Info: caching main/main.jar JarClassLoader: Cached bytes for class com.main.Main.class JarClassLoader: findClass(com.main.Main) JarClassLoader: found com.main.Main in main/main.jar JarClassLoader: findClass(com.a.A) JarClassLoader: found com.a.A in lib/a.jar A() JarClassLoader: findClass(com.b.B) JarClassLoader: found com.b.B in lib/b.jar B()Notice how the
JarClassLoaderkeeps track of where classes came from: for example the
com.a.Aclass is contained inside the
At this stage you should be asking "How does One-JAR decide which is the main class?".
The conventional way would be to have a manifest attribute in the top-level JAR
myapp.jar and require this to be edited before the final jar was
assembled. But this is all unnecessary: One-JAR simply looks for a jar file inside the
sub-directory of the composite JAR file
myapp.jar, and provided that that JAR file has a
attribute, it will be used as the main entry point. This allows an existing main-class JAR file to be bundled inside
a One-JAR archive without further modification!
That's a high-level overview of One-JAR, next we'll get into the details behind the
JarClassLoader and what it does to make all of this
If you're really in a hurry to see One-JAR in action, there is a pre-built One-JAR example which can be downloaded here:
Once you save this file to disk you can run it as shown below. Note that this example is fairly complex (it's really a regression test for the One-JAR product and should be broken out into JUnit tests). We'll be discussing precisely what is going on in the next section.
You can gain more insight into the role of the One-JAR
classloader by running with a
java -Done-jar.verbose command. Don't be perturbed by the failure, it is expected
and caused by pathalogical behaviour in one of the test cases.
$ java -jar one-jar-example.jar Main: com.simontuffs.onejar.example.main.Main.main() DetectClassLoader: Warning: com.simontuffs.onejar.example.main.Test$TestLoader is a ClassLoader DetectClassLoader: Warning: loaded from codesource (jar:file:/C:/work/eclipse-2.1.1/workspace-simontuffs/one-jar/dist/one-jar-example.jar!/main/main.jar
) DetectClassLoader: Warning: and declared 'loadClass(String)'. It may not be able to load classes without being modified. Test: loaded by com.simontuffs.onejar.DetectClassLoader@1888759 Test: codesource is jar:file:/C:/work/eclipse-2.1.1/workspace-simontuffs/one-jar/dist/one-jar-example.jar!/main/main.jar Util: loaded by com.simontuffs.onejar.DetectClassLoader@1888759 Util.sayHello() Test.useResource(/duplicate.txt) OK ------------------------------------------- This is an example of a duplicate resource file named duplicate.txt, placed into main.jar ------------------------------------------- Util.InnerClass loaded by com.simontuffs.onejar.DetectClassLoader@1888759 Util.StaticInnerClass loaded by com.simontuffs.onejar.DetectClassLoader@1888759 Test.useUtil() OK Test.useResource(/main-manifest.mf) OK dumpResource: /main-manifest.mf ------------------------------------------- Manifest-Version: 1.0 Main-Class: com.simontuffs.onejar.example.main.Main ------------------------------------------- Test.useResource(/duplicate.txt) OK dumpResource: /duplicate.txt ------------------------------------------- This is an example of a duplicate resource file named duplicate.txt, placed into main.jar ------------------------------------------- Test.useResource(main.txt) OK dumpResource: main.txt ------------------------------------------- This is main.txt ------------------------------------------- Test.loadCodeSource(): dumping entries in jar:file:/C:/work/eclipse-2.1.1/workspace-simontuffs/one-jar/dist/one-jar-example.jar!/main/main.jar Test: entry=main-manifest.mf Test: entry=duplicate.txt Test: entry=com/simontuffs/onejar/example/util/Duplicate.class Test: entry=com/simontuffs/onejar/example/main/Main.class Test: entry=com/simontuffs/onejar/example/main/Test$TestLoader.class Test: entry=com/simontuffs/onejar/example/main/Test.class Test: entry=com/simontuffs/onejar/example/main/main.txt Creating new TestLoader() loading com.simontuffs.onejar.example.util.Util TestLoader.loadClass(com.simontuffs.onejar.example.util.Util) Test.classLoader() failed: java.lang.ClassNotFoundException: com.simontuffs.onejar.example.util.Util classURL(): Opening onejar resource using new URL(onejar:/com/simontuffs/onejar/example/main/Main.class) classURL(): Opened: onejar:/com/simontuffs/onejar/example/main/Main.class classURL(): OK. classURL(): opening using getResource(/com/simontuffs/onejar/example/main/Main.class) classURL(): OK. Main: finished in 31 ms 1 failure (TODO: JUnit!)