Create a custom Java 1.2-style ClassLoader

The Java 1.2 delegation model simplifies class-loading design and implementation

Developers have two well-known reasons for building custom ClassLoaders: 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 ClassLoaders provides for multiple pseudoapplications to run concurrently in the JVM.

Java 1.1 custom ClassLoader responsibilities

The earlier Java 1.1-style custom ClassLoaders let you load Java classes from all sorts of interesting places. (For information regarding Java 1.1-style custom ClassLoaders, 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 ClassLoaders from having to override it. In fact, new custom ClassLoaders 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 ClassLoaders 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 ClassLoaders 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 ClassLoaders. Chaining ClassLoaders 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 ClassLoaders 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 ClassLoaders 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 ClassLoaders 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 ClassLoaders, 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("store\\MANIFEST.MF");
      manifest = new Manifest(fi);
    }

Resource loading

In addition to loading classes, ClassLoaders 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 ClassLoaders 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 ClassLoaders. 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 ClassLoaders. Using these system methods gives you stricter control of the resources used by the application.

Conclusion

The delegation design for Java 1.2 ClassLoaders 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!

Ken McCrary is a Sun-certified Java developer living in Research Triangle Park, N.C. He has worked on Java projects ranging from Web applets to embedded Java systems. You can visit his Website at http://www.KenMcCrary.com.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more