Some reader favorites:
EJB fundamentals and session beans
Create a scrollable virtual desktop in Swing
Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API
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.
Let's first examine our requirements for a class loader. We'd like one that:
testing or simulating an Internet server on a client.
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.
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.
< g >
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.
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 "<code>//----- 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
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!
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!
Free Download - 5 Minute Product Review. When slow equals Off: Manage the complexity of Web applications - Symphoniq
![]()
Free Download - 5 Minute Product Review. Realize the benefits of real user monitoring in less than an hour. - Symphoniq