The job of our ClassLoader, CCL, is to make sure our code is compiled and up to date.
Here is a description of how it works:
- When a class is requested, see if it exists on disk, in the current directory, or in the appropriate subdirectory.
- If the class is not available, but the source is, call the Java compiler to generate the class file.
- If the class file does exist, check to see if it is older than its source code. If it is older than the source, call the Java compiler to regenerate the class file.
- If the compilation fails, or if for any other reason the class file could not be generated from the existing source, throw a
- If we still don't have the class, maybe it's in some other library, so call
findSystemClassto see if that will work.
- If we still don't have the class, throw a
- Otherwise, return the class.
Before we get too far into our discussion, we should back up a bit and talk about Java compilation. Generally, the Java compiler doesn't just compile the classes you ask it to. It also compiles other classes, if those classes are needed by the classes you've asked it to compile.
The CCL will compile each class in our application, one by one, that needs to be compiled. But, generally speaking, after the compiler compiles the first class, the CCL will find that all the other classes that needed to be compiled have in fact been compiled. Why? The Java compiler employs a rule similar to the one we are using: if a class doesn't exist or is out of date with respect to its source, then it needs to be compiled. In essence, the Java compiler is one step ahead of the CCL, and takes care of most of the work for it.
The CCL reports on what application classes it is compiling as it compiles them. In most cases, you'll see it call the compiler on the main class in your program, and that will be all it does -- a single invocation of the compiler is enough.
There is a case, however, in which some classes don't get compiled on
the first pass. If you load a class by name, using the
Class.forName method, the Java compiler won't know
that this class is needed. In this case, you'll see the CCL run the
Java compiler again to compile this class. The example in The source code
illustrates this process.
To use the CCL, we have to invoke our program in a special way. Instead of running the program directly, like this:
% java Foo arg1 arg2
we run it like this:
% java CCLRun Foo arg1 arg2
CCLRun is a special stub program that creates a CompilingClassLoader and uses it to load up the main class of our program, ensuring that the entire program will be loaded through the CompilingClassLoader. CCLRun uses the Java Reflection API to call the main method of the specified class and to pass the arguments to it. For more details, see The source code .
Included with the source is a set of small classes that illustrate how things work. The main program is a class called
which creates an instance of class
Bar creates an instance of another class called
which is inside a package called
order to illustrate that the CCL works with code in subpackages.
also loads a class by name, namely class
to illustrate this ability
also works with the CCL.
Each class announces that it has been loaded and run. Use The source code
and try it out now. Compile CCLRun and CompilingClassLoader. Make sure you don't compile the other classes (
Boo ) or the CCL won't be of any use, because the classes will already have been compiled.
% java CCLRun Foo arg1 arg2 CCL: Compiling Foo.java... foo! arg1 arg2 bar! arg1 arg2 baz! arg1 arg2 CCL: Compiling Boo.java... Boo!
Note that the first call to the compiler, for
takes care of
baz.Baz as well.
Boo doesn't get called until
Bar tries to load it by name, at which point our CCL has to invoke the compiler
again to compile it.