Having trouble with that class loader? Here's how to use one in Java 1.1 that works either over the 'Net or from a local file
One of the most frequent advanced Java questions I encounter is, โIโm having trouble loading classes over the Internet.โ In fact, in my own experience, when I finally had to deploy an application on an intranet, my class loaders failed. My attempts to take a shortcut and use the built-in RMIClassLoader succeeded with Java 1.0 applets but failed with Java 1.1 programs.
For his โJava In Depthโ column in the October โ96 issue of JavaWorld, Chuck McManis discussed in โThe basics of Java class loaders.โ Based on that column, this Java Tip demonstrates how to successfully use the Java 1.1 class loader and goes a lot further.
Java class loader requirements
Letโs first examine our requirements for a class loader. Weโd like one that:
can load classes from any legitimate URL pointing to a class file
can also load classes from any local class file; this is useful for
testing or simulating an Internet server on a client.
can optionally translate periods in the class name to a preferred character, such as โ_โ; this is useful for placing classes from different packages/directories into the same directory
follows the rules that all class loaders must obey
High-level design
The most flexible approach to the above requirements is these three classes:
public abstract class MultiClassLoader extends ClassLoader
public class URLClassLoader extends MultiClassLoader
public class FileClassLoader extends MultiClassLoader
This allows the type of class loader to be determined at runtime and the rest of the application is unaffected.
Unit test
Hmmmm, fulfilling the above requirements doesnโt sound too hard. Letโs start with a โUse Case,โ namely a code example of how weโll use MultiClassLoader. This is best done by designing our test class, called TestLoader. Now, letโs write the TestLoader unit test class. This shows how to set the class loader type at runtime, and then uses it in a variety of ways to illustrate how to use a class loader effectively. Here is the source code for TestLoader.java.
Letโs examine the heart of the test code:
// Init the preferred class loader
MultiClassLoader loader = null;
if (args.length == 0) {
String root = "http://www.mindspring.com/~happyjac/";
loader = new URLClassLoader(root);
} else {
loader = new FileClassLoader("store");
}
loader.setClassNameReplacementChar('_');
// A series of tests:
Class testClass = null;
try {
testClass = loader.loadClass("Hello");
} catch(Exception ex) {
print("Load failed");
ex.printStackTrace();
return;
}
print("Loaded class " + testClass.getName() );
try {
Runnable hello = (Runnable)testClass.newInstance();
hello.run();
} catch(Exception ex) {
print("Failed to instantiate");
ex.printStackTrace();
}
Good tests are understandable and short. The test first creates an instance of class loader called โloaderโ. Then it uses an optional feature to translate periods to โ_โ.
Next we decide whether to load from the Internet or from a local file, depending on whether any command-line argument was entered. Note that you will need to set the root or filePrefix to whatever source URL youโre using as a base. We have subclassed MultiClassLoader, providing a clean way to provide different specific class sources.
Then we attempt to load the Hello class, and we abort if the load fails. If an exception is thrown, it will be displayed on the command line. This is done with only one line of code, which illustrates the architectural strength of Java:
testClass = loader.loadClass("Hello");
So far, so good. Weโve loaded the class, called testClass. Now we need an instance of testClass, easily done with the newInstance() method. But we also need to cast the Object returned by newInstance() to a class known by the class loader that loaded TestLoader. This is usually the primordial class loader. One way is to provide the desired interface locally, such as EntryPoint.class. But this violates the strategy of thin client, so instead we cast Object to Runnable, which is provided by the Java core API. This is all done in one of the most powerful lines of Java code you will encounter:
Runnable hello = (Runnable)testClass.newInstance();
The Runnable interface has only one method, run(), so we
next do:
hello.run();
And poof! โ weโre done. Thatโs it: three important lines of code, and the rest is done by Java and our MultiClassLoader. This three-line technique is known as โbootstrapping the client.โ Relax and rest awhile, three lines of code is hard work.
A common mistake is to cast the object to something like EntryPoint, and when it works, to assume that allโs well. Actually, the most likely reason it worked is because the primordial or another class loader found your local copy of EntryPoint.class using CLASSPATH when findSystemClass() was called; you probably thought it was loaded from elsewhere.
When Hello starts running, it runs an additional test to prove that MultiClassLoader is used to load โforeignโ classes referenced by Hello, and that casting to foreign interfaces works. The test classes are Hello.java, Worker.java, and Person.java.
Examining MultiClassLoader
Letโs examine the end result of loading Hello.class with our class loader. We now have Hello running. Any classes referenced by Hello will be loaded by the class loader that loaded Hello โ namely MultiClassLoader. The source code is MultiClassLoader.java.
As we can see from our previous test, the key method is:
public Class loadClass(String className) throws ClassNotFoundException {
return (loadClass(className, true));
}
The method loadClass(String className) is a convenience method that calls another method that does the real work. This other method, loadClass(className, true), is the abstract method in java.lang.ClassLoader that all subclasses must implement. Hereโs the code:
public synchronized Class loadClass(String className,
boolean resolveIt) throws ClassNotFoundException {
Class result;
byte[] classBytes;
monitor(">> MultiClassLoader.loadClass(" + className + ", " + resolveIt + ")");
//----- Check our local cache of classes
result = (Class)classes.get(className);
if (result != null) {
monitor(">> returning cached result.");
return result;
}
//----- Check with the primordial class loader
try {
result = super.findSystemClass(className);
monitor(">> returning system class (in CLASSPATH).");
return result;
} catch (ClassNotFoundException e) {
monitor(">> Not a system class.");
}
//----- Try to load it from preferred source
// Note loadClassBytes() is an abstract method
classBytes = loadClassBytes(className);
if (classBytes == null) {
throw new ClassNotFoundException();
}
//----- Define it (parse the class file)
result = defineClass(classBytes, 0, classBytes.length);
if (result == null) {
throw new ClassFormatError();
}
//----- Resolve if necessary
if (resolveIt) resolveClass(result);
// Done
classes.put(className, result);
monitor(">> Returning newly loaded class.");
return result;
}
The method is not very different from Chuckโs original article. Subclassing ClassLoader is much simpler that writing the entire class loader. Note the use of monitor() for debugging. The comments in the code listing are straightforward, so I wonโt give you a detailed explanation here. If youโd like one, though, read Chuckโs original article.
The most important thing loadClass() does is to perform a series of steps in the correct order, to satisfy the design of class loaders. If these steps are out of order, one is missing, or one is improperly implemented, you will witness abnormal behavior or security breaks. Satisfying these requirements cannot be over-emphasized.
Notice the โ//----- Try to load it from preferred source" block. This is the heart of the entire class, and it illustrates the beauty of the class loader concept: All a class loader does is get an array of bytes from a source somewhere and feed these bytes to Java appropriately. Such sweet simplicity! In our case, we are calling an abstract method, implemented by subclasses <code>URLClassLoader or FileClassLoader. Note how you could easily subclass to handle more sources, such as a relational database.
The subclasses source code is URLClassLoader.java and FileClassLoader.java.
Letโs look at URLClassLoderโs key method:
protected byte[] loadClassBytes(String className) {
className = formatClassName(className);
try {
URL url = new URL(urlString + className);
URLConnection connection = url.openConnection();
if (sourceMonitorOn) {
print("Loading from URL: " + connection.getURL() );
}
monitor("Content type is: " + connection.getContentType());
InputStream inputStream = connection.getInputStream();
int length = connection.getContentLength();
monitor("InputStream length = " + length); // Failure if -1
byte[] data = new byte[length];
inputStream.read(data); // Actual byte transfer
inputStream.close();
return data;
} catch(Exception ex) {
print("### URLClassLoader.loadClassBytes() - Exception:");
ex.printStackTrace();
return null;
}
}
Creating loadClassBytes() is not at all difficult, thanks to Javaโs many handy classes. All the real work is done in one line of code: inputStream.read(data);
A final word of caution: Each subclass of MultiClassLoader should be used for only one source. Do not change a class loaderโs source after the class loader is instantiated. This will prevent disasters like a class with the same name but a different source being loaded from the wrong source โ which could happen if the source was changed dynamically. It will also make security breaks more difficult. Avoid the temptation to reset the source. Instead, design your subclasses to set the preferred source once and only once, so help you Java!
How to run your very own test
First download the following files from here:
Hello.class
test_store_Person.class
test_store_Worker.class
Warning: These are files that run as an application โ not as an applet. They are trusted and are merely compiled classes from the source files mentioned above.
Download MultiClassLoader, URLClassLoader, FileClassLoader, and TestLoader (all .java files). Put them in the same directory, named โtest,โ and compile TestLoader, which automatically also compiles the other classes. Open your Internet connection, such as by dial-up. Then run TestLoader via the usual java TestLoader. You should see:
Loading from URL: http://www.mindspring.com/~happyjac/Hello.class
Loaded class test.store.Hello
Hello class instantiated
Hello.run() called
Loading from URL: http://www.mindspring.com/~happyjac/test_store_Worker.class
Loading from URL: http://www.mindspring.com/~happyjac/test_store_Person.class
Worker class instantiated
Worker.startWorking() called
Worker class instantiated
Person first name is FirstName
Tested casting Worker to Person
Test complete
To test the ability to load classes from a file, add a subdirectory to the โtestโ directory named โstore.โ Download the files Hello.java, Person.java, and Worker.java. Compile them. Rename Person.class to test_store_Person.class and Worker.class to test_store_Worker.class. Then on the โtestโ directory, run java TestLoader x without your dial-up open. You should see output similar to the first test except that itโs loaded locally.
Thatโs it. Have fun with your very own class loader. It gives you the power (with permission) to load a class from anywhere on the network. This means that if, as Sun claims, โthe network is the computer,โ then you are now the networkโs absolute master. Once you understand the power of custom class loaders, they are a viable alternative to browsers for thin client. Thanks to Chuck McManis for the concepts in his JavaWorld column on class loaders!


