The Java 1.2 delegation model simplifies class-loading design and implementation
Developers have two well-known reasons for building custom ClassLoader
s: providing support for a new class repository and partitioning user code in a server. The applet ClassLoader
is an example of the former. It introduces the ability to load classes from an HTTP server. The latter purpose — partitioning user code in a server — is less frequent. For example, consider a servlet engine responsible for running servlets created by several developers. Those developers may be unaware of each other, as in a commercial Web-hosting environment, and they will want their Java code to execute independently of any other code using the servlet runner. A custom ClassLoader
can maintain a separate ClassLoader
for each developer’s servlet. Since a class is identified in a Java virtual machine (JVM) by its full name and the ClassLoader
that loaded it, classes loaded by a different ClassLoader
are effectively separated. This use of custom ClassLoader
s provides for multiple pseudoapplications to run concurrently in the JVM.
Java 1.1 custom ClassLoader responsibilities
The earlier Java 1.1-style custom ClassLoader
s let you load Java classes from all sorts of interesting places. (For information regarding Java 1.1-style custom ClassLoader
s, see Resources.) However, this capability carried the price of complexity. The abstract class java.lang.ClassLoader
required a custom ClassLoader
to implement a single method: loadClass()
. Nothing difficult about implementing a single method — pretty simple, right? Wrong!
The loadClass()
method required the custom ClassLoader
to implement a laundry list of functions. In the Java 1.1 custom loadClass()
they include the following:
- Check to see whether or not the class has already been loaded
- Check to see whether or not the class has been loaded by the system loader
- Load the class from the respository
- Define the class
- Resolve the class
- Return the new class to the caller
As you can see, this is quite a list, given that you are just trying to customize the way classes are input into the virtual machine (VM). Essentially, the java.lang.ClassLoader
design forced you to do all the work for loading a class, rather than only the work required to bring a class into the VM in a new way.
Java 1.2 class-loading changes
For Java 1.2, a delegation design was put in place for java.lang.ClassLoader
. In the delegation design, a custom ClassLoader
delegates class loading to its parent. A ClassLoader
parent can be either the bootstrap ClassLoader
or another custom ClassLoader
. In the event the parent ClassLoader
can’t load a class, a new method, named findClass()
, is called on the ClassLoader
subclass. In this manner, the custom ClassLoader
is responsible for loading only the classes not available to the parent — presumably classes that come from a new type of class repository.
Getting down to the nuts and bolts of the code, the loadClass()
method is no longer abstract, relieving custom ClassLoader
s from having to override it. In fact, new custom ClassLoader
s should not override loadClass()
; instead they should override the new findClass()
method to contain the custom class-loading logic. The java.lang.ClassLoader
implementation of findClass()
throws a ClassNotFoundException
, essentially a no-op awaiting implementation by a custom ClassLoader
.
Create a Java 1.2-delegation-style ClassLoader
Now I’ll explain how to write a simple 1.2-style custom ClassLoader
. First, you will subclass java.lang.ClassLoader
, overriding findClass()
as described earlier. The ClassLoader
source file is available in Resources. I should note that the updates to java.lang.ClassLoader
for Java 1.2 are backward compatible, so Java 1.1-style custom ClassLoader
s will continue to work on a Java 1.2 virtual machine as they did on Java 1.1. Since the Java 1.2 version of java.lang.ClassLoader
implements the delegation design in the loadClass()
method, Java 1.1 custom ClassLoader
s override the delegation code because they provide their own loadClass()
implementation. Remember that in Java 1.1, java.lang.ClassLoader
‘s loadClass()
method was abstract, requiring subclasses to provide an implementation.
This simple ClassLoader
will load Java classes directly from the file system into subdirectories under a fixed directory named store,
corresponding to their package specification. The ClassLoader
will use Chuck McManis’s technique (see Resources) of ensuring that the system ClassLoader
will not find the classes you want to load by renaming the class files with the extension .impl
Any additional files used with the classes will be loaded from the store
directory.
Here is an excerpt from the findClass()
method in the SimpleClassLoader
:
FileInputStream fi = null;
try
{
System.out.println("Simple1_2ClassLoader finding class: " + name);
String path = name.replace('.', '/');
fi = new FileInputStream("store/" + path + ".impl");
byte[] classBytes = new byte[fi.available()];
fi.read(classBytes);
definePackage(name);
return defineClass(name, classBytes, 0, classBytes.length);
}
The findClass()
method is only responsible for loading the class bytes and returning a defined class. The method defineClass
will convert the raw class bytes into a Java class suitable for JVM use. The parent ClassLoader
, either another custom ClassLoader
or the bootstrap ClassLoader
, has already made several attempts at loading the class. It first attempts to find the class among the set of classes that have been previously loaded. If the class has not been previously loaded, it will call loadClass()
on the parent ClassLoader
(if there is one) or call it on the bootstrap ClassLoader
. Only when those attempts have failed will your custom ClassLoader
be called to load the class via findClass()
. Since a lot of the work is done for you, the 1.2-custom ClassLoader
is much simpler than its Java 1.1.x counterpart.
Chaining ClassLoaders together
Using the delegation model, you can chain together custom ClassLoader
s. Chaining ClassLoader
s lets you bring together multiple unique class-loading mechanisms. A new constructor for java.lang.ClassLoader
that lets you assign a parent ClassLoader
has been added to Java 1.2. Class-loading requests are delegated to the parent ClassLoader
, and child ClassLoader
s receive requests that the parent is unable to fulfill. If the no-argument ClassLoader
constructor is used, the system ClassLoader
is automatically assigned as the parent ClassLoader
for delegation.
The following code sample shows the constructor you must implement to enable ClassLoader
chaining. For completeness, custom ClassLoader
s should provide this constructor to allow parent ClassLoader
assignment.
Simple1_2ClassLoader(ClassLoader parent)
{
super(parent);
init();
}
Class-loading requests will be delegated to the parent ClassLoader
, which will have the first shot at loading a class. This feature lets you create a chain of ClassLoader
s that work together without having to use inheritance. Using an inheritance design would result in code with more interdependencies between the classes. Inheritance would make each subclass exposed to errors introduced by changing the superclass. Note that a findClass()
method in a custom ClassLoader
does not need to call its superclass’s implementation of findClass()
. However, it should throw a ClassNotFoundException
whenever it is not able to load the class. When this exception is thrown, another custom ClassLoader
in the chain has the chance to load it. In the event the class is never loaded, the ClassNotFoundException
will of course be thrown to the application code.
Other Java 1.2 enhancements to java.lang.ClassLoader
For Java 1.2, runtime package versioning is available. That is, at runtime you can query the specification and implementation version information for a class. This capability is implemented via modifications to several classes in the Java runtime. The ClassLoader
is central to the modifications that support runtime versioning.
The abstract class java.lang.ClassLoader
contains the method definePackage()
to define a package. Once a package is defined, you can use the new Java 1.2 java.lang.Package
class to identify classes loaded from that package. However, the ClassLoader
subclass actually defines a package. This means that java.lang.ClassLoader
subclasses, such as java.net.URLClassLoader
, actually contain the logic to determine the package-versioning information and call the definePackage
method on the java.lang.ClassLoader
superclass.
Thus, a custom ClassLoader
may need to have the same logic to identify package information and make the appropriate call to java.lang.ClassLoader
to define the package at runtime. The package information needs to be written in a manifest, so your custom ClassLoader
must read the manifest and define the packages if the runtime-package-versioning system is to work. Of course, for some custom ClassLoader
s, it may be determined that package versioning is not needed, but that should be a conscious choice, not an oversight.
I will illustrate the use of package versioning by enabling the simple ClassLoader
to read a manifest from the store
directory. Normally, the manifest would be contained in a jar or zip file; this is just an example:
// Check for a manifest in the store directory
try
{
fi = new FileInputStream("storeMANIFEST.MF");
manifest = new Manifest(fi);
}
Resource loading
In addition to loading classes, ClassLoader
s also locate resources in the repositories they are managing. These resources may be image files, audio files, or other types of data. For completeness, custom ClassLoader
s should also provide this capability.
A ClassLoader
locates resources using the delegation model in the same manner it did to load classes. A new method, findResource()
, has been added to support delegation-style resource lookup for custom ClassLoader
s. This method functions in a fashion similar to findClass()
. The custom ClassLoader
must override findResource()
and expect that findResource
will be called when the parent ClassLoader
cannot find the requested resource. The parent ClassLoader
will be calling findResource()
in the context of an application call to getResource()
. Here is a sample implementation of findResource()
from the simple ClassLoader
:
protected URL findResource(String name)
{
File searchResource = new File("store" + name);
URL result = null;
if ( searchResource.exists() )
{
try
{
return searchResource.toURL();
}
catch (MalformedURLException mfe)
{
}
}
return result;
}
The getResource()
method returns the URL of a single located resource. Given that a ClassLoader
may be managing several repositories — for example, multiple jar files — a resource may be found in more than one place. This realization led to the introduction of the getResources()
method, which returns an enumeration of URLs indicating the collection of resources found.
Note that if a user wants to look for resources strictly from the system classpath, the methods getSystemResource()
and getSystemResources()
are available. These methods use the system ClassLoader
to locate resources and don’t delegate to custom ClassLoader
s. Using these system methods gives you stricter control of the resources used by the application.
Conclusion
The delegation design for Java 1.2 ClassLoader
s is a significant improvement over the earlier versions of ClassLoader
. The earlier design required each ClassLoader
subclass to reimplement a prescribed set of functions. Consequently, this reimplemented functionality had to be retested in each ClassLoader
subclass. This is an example of a bottom-heavy design, where logic that should be in a superclass is put into each subclass instead.
With the new design for Java 1.2, those problems have been corrected. The core logic for class loading is now in the loadClass()
method of java.lang.ClassLoader
, which contains a “hook” that enables a subclass to find classes from new repositories. This solves the problem of duplicating code in subclasses to implement just the basic requirements of class loading. It also enables you to concentrate on your own unique ClassLoader
functionality with the plumbing already provided. Happy class loading!