Java ClassLoader – Part 2 – Write your own ClassLoader

This is the second part of series of articles about Java’s ClassLoader. We will show here how you can write your own simple ClassLoader and “replace” the default system ClassLoader with your version.

We have to extend the java.lang.ClassLoader class and implement some of its crucial methods, like loadClass(String name). This method is run every time somebody requests a class inside the code, and receives as a parameter the full name of the class to load. In our implementation we will print out this name, so to know when our method was called, and then load the class with our class loader if the class is in “javablogging” package. If the class is not in “javablogging”, we will use the super.loadClass() method, which will pass the request to the parent class loader. (Please note that in this article we omit the “package” and “import” statements to make the code shorter, but every class should be inside the “javablogging” package).

/**
 * Our custom implementation of the ClassLoader.
 * For any of classes from "javablogging" package
 * it will use its {@link CustomClassLoader#getClass()}
 * method to load it from the specific .class file. For any
 * other class it will use the super.loadClass() method
 * from ClassLoader, which will eventually pass the
 * request to the parent.
 *
 */
public class CustomClassLoader extends ClassLoader {

     /**
     * Parent ClassLoader passed to this constructor
     * will be used if this ClassLoader can not resolve a
     * particular class.
     *
     * @param parent Parent ClassLoader
     *              (may be from getClass().getClassLoader())
     */
    public CustomClassLoader(ClassLoader parent) {
        super(parent);
    }

     /**
     * Loads a given class from .class file just like
     * the default ClassLoader. This method could be
     * changed to load the class over network from some
     * other server or from the database.
     *
     * @param name Full class name
     */
    private Class<?> getClass(String name)
        throws ClassNotFoundException {
        // We are getting a name that looks like
        // javablogging.package.ClassToLoad
        // and we have to convert it into the .class file name
        // like javablogging/package/ClassToLoad.class
        String file = name.replace('.', File.separatorChar)
            + ".class";
        byte[] b = null;
        try {
            // This loads the byte code data from the file
            b = loadClassData(file);
            // defineClass is inherited from the ClassLoader class
            // and converts the byte array into a Class
            Class<?> c = defineClass(name, b, 0, b.length);
            resolveClass(c);
            return c;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

     /**
     * Every request for a class passes through this method.
     * If the requested class is in "javablogging" package,
     * it will load it using the
     * {@link CustomClassLoader#getClass()} method.
     * If not, it will use the super.loadClass() method
     * which in turn will pass the request to the parent.
     *
     * @param name
     *            Full class name
     */
    @Override
    public Class<?> loadClass(String name)
        throws ClassNotFoundException {
        System.out.println("loading class '" + name + "'");
        if (name.startsWith("javablogging.")) {
            return getClass(name);
        }
        return super.loadClass(name);
    }

     /**
     * Loads a given file (presumably .class) into a byte array.
     * The file should be accessible as a resource, for example
     * it could be located on the classpath.
     *
     * @param name File name to load
     * @return Byte array read from the file
     * @throws IOException Is thrown when there
     *               was some problem reading the file
     */
    private byte[] loadClassData(String name) throws IOException {
        // Opening the file
        InputStream stream = getClass().getClassLoader()
            .getResourceAsStream(name);
        int size = stream.available();
        byte buff[] = new byte[size];
        DataInputStream in = new DataInputStream(stream);
        // Reading the binary data
        in.readFully(buff);
        in.close();
        return buff;
    }
}

For a bunch of classes from the “javablogging” package (we will see one of them in a minute) we will load them ourselves from the .class files instead of passing a request for them to the parent class loader. We do it for two reasons: first is to show that in a real-world example you could easily implement the getClass() method to load the class code from some other source, like from the TCP/IP connection or from the database, second is that when we load the class directly in our class loader, instead of passing further the request, our class loader will become default class loader for the newly created class. It will be returned every time from the class.getClassLoader() method and every new request for a class, from an instance of the created class, will pass through our class loader.

It may seem a little confusing, so let’s see how it works on an example. We will load a class using this class loader, and from inside the class we will access some other class, which could be anything, like java.util.Integer.

The runMe() method of the class below simply creates an instance of Integer class and prints it to the console. We will load this class in a minute with our class loader.

public class IntegerPrinter {
    /**
     * Creates an instance of Integer class and prints it.
     */
    public void runMe() {
        System.out.println(new Integer(4));
    }
}

Now the main method that will do our experiment. We create there an instance of our custom class loader, and load our IntegerPrinter class with it using the full class name “javablogging.IntegerPrinter”. We then create a new instance of the IntegerPrinter and run its runMe() method using Reflection.

Note that we could NOT cast ‘instance’ at this point to IntegerPrinter and run its ‘runMe’ method directly, because we would get a ClassCastException! It would say something like “could not cast javablogging.IntegerPrinter to javablogging.IntegerPrinter” which seems very weird, but has good reasons. We loaded the IntegerPrinter class with our custom class loader, but the variable of type IntegerType which we would declare in IntegerPrinterTest would have been loaded with the default system class loader. To the JVM they would be classes from different namespaces, hence impossible to cast one to another.

public class IntegerPrinterTest {
    /**
     * This main method shows a use of our CustomClassLoader for
     * loading some class and running it. All the objects referenced
     * from the IntegerPrinter class will be loaded with
     * our CustomClassLoader.
     */
    public static void main(String[] args) throws Exception {
        CustomClassLoader loader = new CustomClassLoader(
            StaticAccessorTest.class.getClassLoader());
        Class<?> clazz =
            loader.loadClass("javablogging.IntegerPrinter");
        Object instance = clazz.newInstance();
        clazz.getMethod("runMe").invoke(instance);
    }
}

When we run our main method, we will get following output:


loading class 'javablogging.IntegerPrinter'
loading class 'java.lang.Object'
loading class 'java.lang.System'
loading class 'java.lang.Integer'
loading class 'java.io.PrintStream'
4

First line is obvious, because we called ourselves the loadClass() method of our class loader. But where do the other lines come from? We did not call loadClass() for them. Since our class loader became the default class loader for the created class, every request for any other class from inside IntegerPrinter will go through our CustomClassLoader. We dont have to call loadClass() any more, it will be called automatically by the JVM! When you look at IntegerPrinter’s code, you will see that we use there System class, and its “out” variable which is of type PrintStream. We also create an Integer, and since IntegerPrinter, like any other class, extends java.lang.Object, we also have to load the Object class. All these requests go now through our CustomClassLoader.

One important thing to understand here is that we replaced the default system class loader only for one loaded class – IntegerPrinter. What we did has still no influence on default class loader used for example by class IntegerPrinterTest with its main method. There is however a way to replace the class loader even before running the main method, and we will show it in one of next articles.

Please note, that if you were about to write a real-world class loader, you would probably extend the URLClassLoader, because the part of loading a class from a file is there already implemented. Also, real class loaders normally ask their parent to load a class BEFORE trying to load it themselves. In our example, for the classes in “javablogging” package, we do load them without asking the parent.

Leave a comment