No, the extension jars cannot be contained in the referring jar. Okay, I am incorrect.
Tom
But, I don't have to be wrong, you can just tweak the process to suit your
needs:
listing contains: 3 files
A. create a manifesto:
The manifest usually contains a class path that points to an external
location according to the extension mechanism. But it's really little
effort to make the class loader work the way YOU want.
I am using a small class that does nothing more than instantiate a new
class loader and launching the main class.
<code file="manifesto">
Manifest-Version: 1.0
Created-By: _nuhb_
Main-Class: Boot
Class-Path: application.jar
</code>
B. Booting your VM.
Basically when the VM has started and called main, the only class loader
active is the system class loader. Using a class loader has a lot of
benefits (and I am still discovering more of them). What this code does is
1) Obtain the URL to the jar file and pass this to the init() method.
2) Read the main attributes of the JAR that contains the class Boot.
3) There in an attribute "Class-Path" must be defined just as with the
extension mechanism; prevents confusion and can do no harm.
4) Then it parses each Class-Path entry; malformed url's are simply
rejected. People ought to package stuff correctly.
5) Create an array of URL's and pass this to the boot() method.
6) Instantiate a new class loader and use the URL's as defined in the
classpath to be the classpath of the class loader.
7) Currently the code is hard coded, you can manipulate this anyway you
like, and make it more dynamic.
8) Then the classloader will load the class as defined by the string
passed to the boot() method and instantiate it, since all entries of the
class path have been added, there's no problem with jars enclosed in
another jar.
<code file="Boot.java">
// GPL
// general disclaimer: people are known to get blind from using this code.
import java.util.*;
import java.net.*;
import java.util.jar.*;
import java.io.*;
public
class Boot
{
/**
* @param baseURL is the main JAR, format-> jar:....!/entry
* @param mainClass fqn of class to load, must be runnable in version.
* @param manifestURL url of the main manifest
**/
protected
void init(final URL baseURL, final URL manifestURL, final String mainClass)
{
final Manifest manifest;
final StringTokenizer tokenizer;
final List list;
try
{
System.out.println("Manifest url:"+manifestURL);
list = new LinkedList();
manifest = new Manifest(manifestURL.openStream());
for (Iterator i=manifest.getMainAttributes().keySet().iterator();i.hasNext()
{
System.out.println(i.next());
}
tokenizer = new StringTokenizer(manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH));
while (tokenizer.hasMoreTokens())
{
try
{
list.add(new URL(baseURL+""+tokenizer.nextToken()));
}
catch (MalformedURLException possibleSoWhat) {}
}
boot((URL[])list.toArray(new URL[0]),mainClass);
}
catch (NullPointerException noURL) {noURL.printStackTrace();}
catch (IOException noConnection) {noConnection.printStackTrace();}
}
/**
* @param urls URLs defining the classpath for the application
* @param mainClass name of the class to start as a thread
*/
protected
void boot(final URL[] urls, final String mainClass)
{
URLClassLoader loader;
try
{
loader = ((URLClassLoader)getClass().getClassLoader()).newInstance(urls);
new Thread((Runnable)Class.forName(mainClass).newInstance()).start();
}
catch (ClassNotFoundException obeyYourParents) {}
catch (IllegalAccessException eatMoreFruit) {}
catch (InstantiationException spreadMoney) {}
catch (ClassCastException stupidProgrammer) {}
}
public
static
void main(String[] args)
throws Exception
{
Boot boot;
URL manifestURL;
URL baseURL;
// retrieve manifest relative to this class.
baseURL=new URL("jar:"+((JarURLConnection)Boot.class.getResource((Boot.class.getName()+".class")).openConnection()).getJarFileURL()+"!/");
manifestURL = new URL(baseURL+"META-INF/MANIFEST.MF");
System.out.println
(
"This classloader is a subclass of URLClassLoader:"+
URLClassLoader.class.isAssignableFrom
(
new Boot().getClass().getClassLoader().getClass()
)
);
boot = new Boot();
boot.init(baseURL,manifestURL,"Start");
}
}
</code>
C.
Here's just a simple Runnable that references a class in application.jar
statically. This causes no problems as Boot.class does not contain static
references to the Start class.
<code file="Start.java">
public class Start implements Runnable{ public void run() {.out.println("I
found a B:"+new B()); }}
</code>
D.
Just an empty class, to prove the class is located
<code file="B.java">
public class B {}
</code>
E.
Contents of the jar and how to make them:
<code file="application.jar">
$ javac B.java
$ jar cf application.jar B.class
</code>
<code file="boot.jar">
$ javac Boot.java Start.java
$ jar cfm manifesto boot.jar Boot.class Start.class application.jar
</code>
F.
How I did it with the above listings:
<code>
~ $ jikes Start.java Boot.java B.java ; jar cf b.jar B.class; jar cmvf manifesto a.jar Boot.class Start.class; rm B.class Start.class Boot.class ; java -jar a.jar
added manifest
adding: Boot.class(in = 3906) (out= 2034)(deflated 47%)
adding: Start.class(in = 600) (out= 376)(deflated 37%)
This classloader is a subclass of URLClassLoader:true
Manifest url:jar:file:/home/bhun/a.jar!/META-INF/MANIFEST.MF
Class-Path
Created-By
Main-Class
Manifest-Version
I found a B:B@187aeca
</code>
QED.
Have fun.