Description
The class loader to load classes, native libraries and resources from
the top JAR and from JARs inside the top JAR.
The top JAR very often is referred as an executable, standalone or uber JAR.
The class loader looks through the hierarchy of JARs and allows nested JARs.
The class loader delegates class loading to the parent class loader and
successfully loads classes, native libraries and resources when it works
not in a JAR environment. The initial revision was published in September 2007.
This class loader gives you an option to deploy your application in
a single deployment JAR.
This means that all third party libraries in JARs (even signed JARs), native libraries
and resources could be put into this single deployment JAR in their original state.
Access to any resource in this single deployment JAR is transparent.
All JARs with all nested JARs and all native libraries in the deployment JAR
are considered to be added to the application classpath regardless
of their names and locations in the deployment JAR.
The class loader is a single class ~1300 lines (including ~500 lines of comments and instructions in JavaDoc).
There are multiple benefits of using this class loader to deliver Java application in a single JAR:
- Simplifies deployment.
Only a single JAR is deployed.
- Obfuscates dependencies.
All dependent JARs are hidden into a single deployment JAR.
- Simplifies application support.
Reduces risk of lost, replaced or misused dependent JARs.
- Simplifies classpath.
All dependent JARs in deployment JAR are considered in the classpath.
- Simplifies native libraries loading.
All native libraries in deployment JAR are considered in the native libraries path.
- Eliminates native libraries loading issues.
Reduces risk of lost, replaced or misused native libraries.
- Simplifies resources loading.
All resources in deployment JAR are considered in the classpath.
- Convenient in development stage.
Transparent loading classes, native libraries and resources from a JAR or file system.
- Allows multiple entry points (applications).
The JAR may contain multiple classes with
main() method and multiple applets.
- Allows shaded class loading.
You may override any class or resource replacing the original class or resource in the dependent JAR.
See details in Shaded class loading section.
- Supports WebStart / JNLP.
WebStart works out of the box.
See details in WebStart / JNLP section.
- Supports extension JCE providers.
The deployment JAR may contain JCE extension provider JAR.
(for example: Bouncy Castle)
- Java applets.
Any level of complexity for Java applets with dependent JARs, native libraries and
resources packed into a single JAR.
See details in Java Applet section.
- Small footprint.
JARs content is enumerated but do not stored in memory. Not used entries in JARs do not consume memory.
The class loader could be used without any special knowledge of Java class loading
and without customizing or learning its internals.
It works for you out of the box. Instructions for how to use this class loader are in
How to use the JarClassLoader section and in the class JavaDoc.
See also discussion Using JARs.
It is is very simple to use in comparison to other
available JAR class loaders. It does not require JAR manifest
or system environment modifications, special application build,
strict JAR structure or any kind of properties or settings.
Just add this class loader and adjusted trivial Launcher class to your application.
The Launcher class has only 12 lines of code.
This class loader resolves feature request
Bug 4648386
posted in March 2002 against JDK 1.2 which is still not resolved in JDK-7.
It had multiple votes and was listed in a Sun's
Top 25 RFE's (broken link).
The link is broken because it was not transfered to Oracle.
For some reasons Sun and later Oracle ignored this request.
The class loader was tested with Java 5 and Java 6 on WinXP and Linux platforms.
Similar products
There are many free JAR class loaders available on the web.
-
Classworlds
(also here)
The package is titled advanced classloader framework.
The version 1.0 R3 was published in 2003.
The final release is not available.
The package consists of 17 files with a total size of ~3600 lines.
It does not support loading native libraries.
It also requires an external configuration file.
There are no clear instructions for how to use the package.
-
UberJAR
Based on Classworlds package.
Requires configuration file and strict directory structure.
Does not support native libraries.
Documentation is minimal.
-
Meta Jar Utilities
The version 1.7.1 was published in 2002.
The package consists of 6 files with a total size of ~2200 lines.
The package does not support loading native libraries.
There are two class loaders in the package.
These class loaders have code that is duplicate and commented out.
Instructions are not clear as to how and when the class loaders should be used.
-
One-JAR
The version 0.97 RC5 was published in 2010.
The package consists of 6 core files with a total size of ~2200 lines.
This is the most mature product in comparison with others.
The package supports loading native libraries.
The web site contains too many confusing JARs to download:
appgen, sdk, example, dll, ant-task, boot.
Nested JARs are not supported. Does not allow shaded class loading.
Memory hog, all JARs content is stored in memory even for not used resources.
The class loader requires a strictly specified root JAR structure.
The class loader is controlled by
MANIFEST.MF attributes.
The package is over-engineered.
-
Embedded jar classloader in under 100 lines
Published in October 2008.
It is essentially a simplified version of our
JarClassLoader .
The class loader is controlled by MANIFEST.MF attribute.
It cannot load native libraries and external LookAndFeel classes.
Documentation is minimal.
-
Eclipse 3.4 JDT: Runnable JAR export wizard
Specific to Eclipse. Combines all classes into a single JAR file.
Does not work with signed JARs and native libraries.
-
Fat Jar Eclipse Plug-In
Specific to Eclipse. Integrates One-JAR.
-
(JCL) Jar Class Loader
Contains ~3500 lines of code in 25 files. Requires xml configuration file.
Supports Spring. Does not support native libraries. Uses Log4j.
Documentation is missing.
-
Maven Shade Plugin
Combines all JARs content into a single JAR which may lead to conflicts.
No special class loader is required.
Configuration with Maven knowledge might be required.
Does not work with signed JARs and native libraries.
Nicely documented, there a many resources on a web including tutorials,
for example
here.
-
JarJar
Utility that repackages classes from multiple JARs into a single one.
No special class loader is required.
None of these class loaders are perfect or easy to use.
The summary of the main issues in majority of them:
- Requirement to specify main JAR entry in the
MANIFEST.MF or configuration file.
This complicates the use and eliminates option to have multiple entry points in a JAR.
- Requirement to have strict directories structure.
- Native libraries are not supported.
- Dependency on other software, e.g. Eclipse, Log4j.
- Missing or not sufficient documentation.
How to use the JarClassLoader
Follow these steps:
1. Create your main root JAR.
Put in it all classes, resources, native libraries and other JARs required
by your application in any location (directory path).
JARs could be nested, i.e. you may have JARs in other JARs with any level of nesting.
JarClassLoader ignores location of JARs and native libraries in the main root JAR.
This gives you an option to organize main root JAR directory structure content.
2. Add class
com.jdotsoft.jarloader.JarClassLoader
to the main root JAR.
3. Create a launcher class using the following template
and add it to the main root JAR.
You could have multiple launcher classes in the JAR
to start different applications from the same root JAR.
package com.mycompany;
import com.jdotsoft.jarloader.JarClassLoader;
public class MyAppLauncher {
public static void main(String[] args) {
JarClassLoader jcl = new JarClassLoader();
try {
jcl.invokeMain("com.mycompany.MyApp", args);
} catch (Throwable e) {
e.printStackTrace();
}
} // main()
} // class MyAppLauncher
|
Update the package declaration in the example above and rename the class.
Update the parameter in the method JarClassLoader.invokeMain()
with package + classname of your application main class.
Pick any name for the package and class name for the launcher and your main class.
4. Start your application using the launcher class com.mycompany.MyAppLauncher.main()
created in the previous step with one of the following commands:
(1) java -cp MyApp.jar com.mycompany.MyAppLauncher Hello World
(2) java -jar MyApp.jar Hello World
The command #2 is available if you have the following entry in the manifest file:
Main-Class: com.mycompany.MyAppLauncher
Any parameters in the examples of command lines given above like Hello World
are optional and passed to your class com.mycompany.MyApp.main() method
by JarClassLoader .
Using the launcher class is transparent in application development stages
when all resources are not yet packed into a single JAR.
The JarClassLoader checks its location: JAR or file system.
If JarClassLoader finds that it is loaded from a file system it
passes all control to the system class loader.
The system class loader than loads classes from a classpath
and handles native libraries accordingly.
Using Maven to build executable JAR
You do not need special Maven plugins to make an executable JAR with JarClassLoader .
The standard Maven plugins will do the job.
Follow these steps:
1. Complete steps in section How to use the JarClassLoader.
2. Add the following <build> section into your project pom-file,
just copy / paste.
Minimal customization and significance of bold elements is discussed below.
In most cases you will need to modify only red value.
<project . . .>
. . .
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<excludeArtifactIds>junit</excludeArtifactIds>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<manifest>
<mainClass>com.mycompany.MyAppLauncher</mainClass>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
|
maven-compiler-plugin provides an option to override default Java compiler version.
Most probably you already have this plugin declared in your pom-file.
The default Maven settings is Java 1.3 compiler.
The value 1.8 overrides it to required by your project Java compiler version.
maven-dependency-plugin executed before packaging,
see prepare-package key value.
JUnit JAR is excluded from the packaging, see junit key value.
The plugin copies all dependent JARs into the
[PROJECT_HOME]/target/classes
directory because Maven defines the default value ${project.build.directory}
as [PROJECT_HOME]/target directory.
maven-jar-plugin does actual packaging from
[PROJECT_HOME]/target/classes directory.
Update the Launcher class name
com.mycompany.MyAppLauncher
with actual class name. You may remove the entry
<mainClass>. . .</mainClass>
altogether if JAR manifest
should not contain the main class name.
Logging
Class loader has flexible logging controlled by system properties.
Developer could specify different logging level and different logging areas.
The log could be redirected to a file.
See details in the class JavaDoc.
The default logging level is ERROR without any output in normal conditions.
Shaded class loading
In many cases developer has to fix a bug in some class or replace resources
in the third party JAR used in the project.
Examples of such replacements could be Java class, JavaScript file,
or hard coded template in dependent JAR.
The JarClassLoader allows this replacement with shaded class loading.
The techniques is very simple. The replacement class or resource should be placed
into the root JAR. The JarClassLoader scans the root JAR first and
then dependent JARs. The requested class or resource is loaded from the root JAR
if found there and further scanning in dependent JARs is not performed.
Multiple JARs may contain identical classes.
Some classes in JAR-A may be shaded by classes with same names in JAR-B.
The class loader may give an answer what classes are actually loaded
and what classes are shaded. This information is logged at WARN log level:
-DJarClassLoader.logger.level=WARN
WebStart / JNLP
The JarClassLoader supports WebStart / JNLP.
Follow these steps to create WebStart / JNLP application in a single JAR:
The implementation is based on idea published by our
user.
See WebStart / JNLP demo in the Demo section.
Java Applet
The JarClassLoader works transparently for applets as well.
Follow these steps to create Java applet in a single JAR:
The applet's launcher class core functionality is similar to plain
Java application launcher discussed in
How to use the JarClassLoader section.
The applet's launcher implements standard
Applet.init() ,
Applet.start() ,
Applet.stop() and
Applet.destroy() methods to pass browser's calls
to underlying com.mycompany.MyApplet applet implementation.
package com.mycompany;
import com.jdotsoft.jarloader.JarClassLoader;
import javax.swing.JApplet;
public class MyAppletLauncher extends JApplet {
private JarClassLoader jcl;
@Override
public void init() {
jcl = new JarClassLoader();
try {
jcl.initApplet("com.mycompany.MyApplet", this);
} catch (Throwable e) {
e.printStackTrace();
}
}
@Override
public void start() {
jcl.startApplet();
}
@Override
public void stop() {
jcl.stopApplet();
}
@Override
public void destroy() {
jcl.destroyApplet();
}
} // class MyAppletLauncher
|
Update the package declaration in the example above and rename the Java applet class.
See Java applet demo in the Demo section.
Temporary files
Class loader extracts inner JARs and native libraries into temporary files, see
design discussion in Design constraints and known limitations
section.
All inner JARs are always extracted into temporary files.
Native libraries are extracted into temporary files only on request to load them.
Temporary files are created by java.io.File.createTempFile()
and marked with a flag deleteOnExit() .
As a result they are deleted by the system on application shutdown when all
temporary files are closed.
The application shutdown event is intercepted by Runtime.addShutdownHook() .
Temporary files with JARs are not deleted on Windows platform if some resources
(other than classes) are loaded from them using getResourceAsStream() .
Temporary files with native libraries are never deleted on Windows platform.
Attempt to explicitly delete these temporary files on shutdown by calling
File.delete() fails because VM does not release these files' handles.
See Bug 4171239:
"This occurs only on Win32, which does not allow a file to be deleted
until all streams on it have been closed."
As a result some temporary files may remain hanging in the file
system after the application shutdown.
The class loader uses a workaround preserving list with failed to delete
temporary files in configuration file .JarClassLoader saved in the [user.home] directory.
The example configuration file location:
[Win7] C:\Users\username\.JarClassLoader
[WinXP] C:\Documents and Settings\username\.JarClassLoader
[Unix] /export/home/username/.JarClassLoader
-or- /home/username/.JarClassLoader
The configuration file is not created if all temporary files are deleted.
Temporary files listed in the configuration files are deleted on the next application run.
It's recommended periodically manually check the directory with
temporary files and delete them if they are present.
These files could accumulate if application process was killed.
The example location for temporary files:
[Win7] C:\Users\username\AppData\Local\Temp\JarClassLoader
[WinXP] C:\Documents and Settings\username\Local Settings\Temp\JarClassLoader
[Unix] /var/tmp/JarClassLoader
The default temporary files location could be changed by modifying
java.io.tmpdir system property:
-Djava.io.tmpdir=<your-temp-dir> (in command line)
System.setProperty("java.io.tmpdir", "<your-temp-dir>"); // in Launcher before new JarClassLoader()
Design constraints and known limitations
See also JarClassLoader class JavaDoc and comments
for details how the class loader works.
The system class loader scans classpath in a well defined order - the order
directories and JARs are declared in a classpath.
The result is predictable: some resource which exists in multiple places will be
always loaded from a directory or JAR which is declared earlier in a classpath.
JarClassLoader loads some class, resource, or native library which
has identical path and present in multiple JARs from unpredictable JAR in a top JAR.
The JarClassLoader does not have any specific scanning order thus making loading
unpredictable.
Using temporary files for loading JARs could be avoided if one of the following
constructors is available in Java API:
java.util.jar.JarFile(java.io.InputStream)
java.util.jar.JarFile(java.util.jar.JarEntry)
java.io.File(java.util.jar.JarEntry)
Alternative to temporary files is
Protocol Handlers with all JARs content stored in memory.
Protocol Handlers are registered using java.protocol.handler.pkgs
System property. This approach is taken by
One-JAR.
It resolves issue with temporary JAR files deletion but consumes large memory
to store all inner JARs in memory.
The number of JARs and their size in current applications could be significant.
It is not reasonable to store the whole JAR in memory while only a few classes
maybe actually needed from it.
Using temporary files for loading native libraries could be avoided if
java.lang.ClassLoader class and VM have an option to load native library
from a stream. Currently VM loads native libraries using method
String findLibrary(String) where String parameter
is an absolute path to the native library.
For example, on Windows platform the absolute path to native library Native.dll
in the main root JAR is defined as
jar:file:/C:/.../MyApp.jar!/lib/Native.dll
This absolute path URL does not work for loading native libraries
and it is not defined at all for nested resources.
The packages and JARs sealing is not supported.
Shaded class loading discussed in a section might expose
Java vulnerability. System class loader does not allow replacing system
Java classes available in rt.jar with custom replica.
Current JarClassLoader implementation allows loading any
class from an application JAR, even a system class, before passing class
loading request to a system class loader. Fortunately the security exception
is thrown for attempt to shade a class in a system package,
for example java.lang.String :
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(Unknown Source)
at java.lang.ClassLoader.defineClassCond(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at com.jdotsoft.jarloader.JarClassLoader.findJarClass(JarClassLoader.java:523)
at com.jdotsoft.jarloader.JarClassLoader.loadClass(JarClassLoader.java:812)
at java.lang.ClassLoader.loadClass(Unknown Source)
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at com.jdotsoft.jarloader.JarClassLoader.invokeMain(JarClassLoader.java:728)
at com.jdotsoft.jarloader.test.LauncherTestConsole.main(LauncherTestConsole.java:18)
|
However, not all classes and resources in rt.jar are protected.
For example, classes in org.w3c.dom package in rt.jar
could be replaced without any warning. This could be considered a feature
and could be used for some cases.
Demo
You could view JarClassLoader in action in the demo.
The demo is packed into a single JAR file
JarClassLoaderDemo.jar
available for download. You can build this JAR yourself using Eclipse project
available in Download section.
The same demo could be executed in multiple fashions:
- Using command line (console version). Download or build demo JAR and execute command
java -jar JarClassLoaderDemo.jar Hello World
- Using command line (GUI version). Download or build demo JAR and execute command
java -cp JarClassLoaderDemo.jar com.jdotsoft.jarloader.test.LauncherTestGUI Hello World
- Using WebStart / JNLP. Click
here to launch demo GUI version using WebStart.
This demo is a self-signed application and thus is untrusted.
See details
here
and
here
how to add http://jdotsoft.com to Exceptions Site List.
- Using Java Applet. Click
here to launch demo GUI version as a Java Applet.
Firefox users: sometimes applet popup page remains blank.
This could be result of conflicting installed JREs or latest Firefox version
did not pickup Java plugin. To resolve the problem close Firefox, unstall all (!) JREs,
install the single one and restart Firefox.
The Java applet demo could be also started as a plain Java application.
Download JarClassLoaderDemo.jar
or build it from the
Eclipse project
and execute command
java -cp JarClassLoaderDemo.jar com.jdotsoft.jarloader.test.LauncherTestApplet Hello World
The JarClassLoaderDemo.jar used for all demos has the following content:
root
- com\jdotsoft\jarloader\JarClassLoader.class
- com\jdotsoft\jarloader\test\TestConsole.class
- com\jdotsoft\jarloader\test\TestGUI.class
- com\jdotsoft\jarloader\test\TestApplet.class
- com\jdotsoft\jarloader\test\LauncherTestConsole.class
- com\jdotsoft\jarloader\test\LauncherTestGUI.class
- com\jdotsoft\jarloader\test\LauncherTestApplet.class
- com\jdotsoft\jarloader\test\ClassForName.class
- com\jdotsoft\jarloader\test\HelloTopJar.class
- com\jdotsoft\jarloader\test\NativeCode.class
- com\jdotsoft\jarloader\test\PrintFile.class
- resources\TopText.txt
- libNative.so
- Native.dll
- substance-lite.jar
- ...multiple classes and resources...
- lib\InnerJar.jar
- com\jdotsoft\jarloader\test\HelloInnerJar.class
- resources\HelloInner.txt
|
The actual demo test code resides in TestConsole class.
The main() method in this class should be used to start the demo
in an exploded environment (make sure that resources are in a classpath).
The TestConsole demo should be started from a JAR using
LauncherTestConsole launcher.
The similar pair TestGUI and LauncherTestGUI
exist for the GUI demo. This demo is a simple JFrame application
with TestConsole output.
External look-and-feel in substance-lite.jar from
http://java.net/projects/substance
is added for this demo and should be tested visually.
The similar pair TestApplet and LauncherTestApplet
exist for the applet demo. This demo is an applet wrapper around
TestGUI code.
Native libraries Native.dll and libNative.so
are built for the Windows and Unix platforms.
Sorry, no Mac native library in the demo.
Be aware of the following security concerns when launching the demo:
- Temporary files will be created on your file system by
JarClassLoader .
- Demo has a native library method call.
The JarClassLoaderDemo.jar is signed to allow access to your
file system (to create temporary files) and execute native calls.
The JarClassLoaderDemo.jlnp for the WebStart / JNLP demo
file has the following:
<security>
<all-permissions/>
</security>
The native library has the following code:
#include "com_jdotsoft_jarloader_test_NativeCode.h"
JNIEXPORT jstring JNICALL
Java_com_jdotsoft_jarloader_test_NativeCode_getString(JNIEnv *env, jobject ob)
{
return (*env)->NewStringUTF(env, "String from native code");
}
|
Download
Click here
to view the class source,
here
to download Eclipse project with demo and unit tests.
License
This Java class is licensed under
GNU General Public License version 3
as published by the Free Software Foundation.
Commercial license is available.
|