The Artima Developer Community
Sponsored Link

Chapter 8 of Inside the Java Virtual Machine
The Linking Model
by Bill Venners

<<  Page 17 of 20  >>

Advertisement

Using a 1.2 User-Defined Class Loader

The class loader described in the previous section, which was originally designed for a 1.1 virtual machine, will still work in 1.2. Although 1.2 added a concrete default implementation of loadClass() to java.lang.ClassLoader, this concrete method can still be overridden in subclasses. Because the contract of loadClass() did not change from 1.1 to 1.2, legacy user-defined class loaders that override loadClass() should still work as expected in 1.2.

The basic contract of loadClass() is this: Given the fully qualified name of the type to find, the loadClass() method should in some way attempt to locate or produce an array of bytes, purportedly in the Java class file format, that define the type. If loadClass() is unable to locate or produce the bytes, it should throw ClassNotFoundException. Otherwise, loadClass() should pass the array of bytes to one of the defineClass() methods declared in class ClassLoader. By passing the byte array to defineClass(), loadClass() asks the virtual machine to import the type represented by the passed byte array into the namespace of this user-defined class loader. When loadClass() calls defineClass() in 1.2, it can also specify a protection domain with which the type data should be associated. When the loadClass() method of a class loader successfully loads a type, it returns a java.lang.Class object to represent the newly loaded type.

The concrete implementation of loadClass() from class java.lang.ClassLoader fullfills the loadClass() method's contract using these four basic steps:

  1. See if the requested type has already been loaded into this class loader's namespace (via findLoadedClass()). If so, return the Class instance for that already-loaded type.
  2. Otherwise, delegate to this class loader's parent loader. If the parent returns a Class instance, return that same Class instance.
  3. Otherwise, invoke findClass(), which should attempt to locate or produce an array of bytes, purportedly in the Java class file format, that define the desired type. If successful, findClass() should pass those bytes to defineClass(), which will attempt to import the type and return a Class instance. If findClass() returns a Classinstance, loadClass() returns that same Class instance.
  4. Otherwise, findClass() completes abruptly with some exception, and loadClass() completes abruptly with the same exception.

Although in 1.2 you can still subclass ClassLoader and override the loadClass() method, the recommended approach to creating your own user-defined class loader in 1.2 is to subclass ClassLoader and implement the findClass() method. The findClass() method looks like this:

// A method declared in class java.lang.ClassLoader:
protected Class findClass(String name)
    throws ClassNotFoundException;

The basic contract of the findClass() method is this: findClass() accepts the fully qualified name of a desired type as its only parameter. findClass() first attempts to locate or produce an array of bytes, purportedly in the Java class file format, that define the type of the requested name. If findClass() is unable to locate or produce the array of bytes, it completes abruptly with ClassNotFoundException. Otherwise, findClass() invokes defineClass(), passing in the requested name, the array of bytes and, optionally, a ProtectionDomain object with which the type should be associated. If defineClass() returns a Class instance for the type, findClass() simply returns that same Class instance to its caller. Otherwise, defineClass() completes abruptly with some exception, and findClass() completes abruptly with the same exception.

Here's a version of GreeterClassLoader that, rather than overriding loadClass(), merely overrides findClass():

// On CD-ROM in file
// linking/ex7/com/artima/greeter/GreeterClassLoader.java
package com.artima.greeter;

import java.io.*;

public class GreeterClassLoader extends ClassLoader {

    // basePath gives the path to which this class
    // loader appends "/.class" to get the
    // full path name of the class file to load
    private String basePath;

    public GreeterClassLoader(String basePath) {

        this.basePath = basePath;
    }

    public GreeterClassLoader(ClassLoader parent, String basePath) {

        super(parent);
        this.basePath = basePath;
    }

    protected Class findClass(String className)
        throws ClassNotFoundException {

        byte classData[];

        // Try to load it from the basePath directory.
        classData = getTypeFromBasePath(className);
        if (classData == null) {
            throw new ClassNotFoundException();
        }

        // Parse it
        return defineClass(className, classData, 0,
            classData.length);
    }

    private byte[] getTypeFromBasePath(String typeName) {

        FileInputStream fis;
        String fileName = basePath + File.separatorChar
            + typeName.replace('.', File.separatorChar)
            + ".class";

        try {
            fis = new FileInputStream(fileName);
        }
        catch (FileNotFoundException e) {
            return null;
        }

        BufferedInputStream bis = new BufferedInputStream(fis);

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {
            int c = bis.read();
            while (c != -1) {
                out.write(c);
                c = bis.read();
            }
        }
        catch (IOException e) {
            return null;
        }

        return out.toByteArray();
    }
}

This version of GreeterClassLoader appears in the linking/ex7 directory of the CD-ROM. All of the source files in linking/ex6, which were described in detail in previous sections, appear unchanged in linking/ex7, except for GreeterClassLoader.java. Where the GreeterClassLoader class, described in the previous section, that overrides loadClass() appears in linking/ex6, the GreeterClassLoader , described in this section, that overrides findClass() appears in linking/ex7.

This second version of GreeterClassLoader declares one instance variable, basePath, which is a String that is used to store the directory path in which findClass() should look for the class file of the type it has been requested to load. The basePath String is the only parameter passed to GreetingClassLoader's 1-arg constructor. Because the 1-arg constructor accepts no reference to a caller-specified parent class loader, this class loader can't invoke the superclass constructor that takes a reference to a user-defined class loader. Thus, it simply invokes the superclass's no-arg constructor by default, which sets this class loader's parent to be the system class loader. The other constructor (the 2-arg constructor), however, accepts a reference to a user-defined class loader instance as well as the basePath String. This constructor explicitly invokes the superclass's 1-arg constructor, passing along the reference. The superclass sets this class loader's parent to be the passed user-defined class loader instance.

By comparing the implementation of findClass() in this version of GreeterClassLoader with the implementation of loadClass() in the previous version of GreeterClassLoader, you can easily see how much simpler it is to write findClass() than loadClass(). You have much less to worry about when you write findClass(), and fewer opportunities to make mistakes. findClass() merely invokes getTypeFromBasePath() to attempt in this user-defined class loader's custom way to load the requested type. If getTypeFromBasePath() is unable to locate the requested type in the basePath directory, it returns null and findClass() throws a ClassNotFoundException. Otherwise, getTypeFromBasePath() returns the array of bytes, which findClass() simply passes on to defineClass(). If defineClass() returns a reference to a Class instance to represent the successfully loaded type, findClass() returns that same reference. Otherwise, defineClass() completes abruptly with an exception, which causes findClass() to complete abruptly with the same exception.

The findClass() method's contract is a subset of the loadClass() method's contract. findClass() isolates the only two parts of loadClass() that should in general be customized by subclasses of java.lang.ClassLoader:

  1. the custom manner in which an array of bytes is located or produced given a fully qualified type name
  2. optionally, the custom manner in which a type's protection domain is determined

When an implementation of findClass() performs these two tasks, the result is an array of bytes and a reference to a ProtectionDomain object. findClass() passes both the byte array and the ProtectionDomain reference to defineClass().

<<  Page 17 of 20  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use