调用 Java 编译器来生成类文件。
如果该类已存在,检查它是否比源码旧。如果是,调用 Java 编译器来重新生成类文件。
如果编译失败,或者由于其它原因不能从现有的源码中生成类文件,返回 ClassNotFoundException。
如果仍然没有该类,也许它在其它库中,所以调用 findSystemClass 来寻找该类。
如果还是没有,则返回 ClassNotFoundException。
否则,返回该类。
调用 findLoadedClass 来查看是否存在已装入的类。
如果没有,那么采用那种特殊的神奇方式来获取原始字节。
如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
如果还没有类,返回 ClassNotFoundException。
否则,将类返回给调用
程序。
Java 编译的工作方式
在深入讨论之前,应该先退一步,讨论 Java 编译。通常,Java 编译器不只是编译您要求它编译的类。它还会编译其它类,如果这些类是您要求编译的类所需要的类。
CCL 逐个编译应用程序中的需要编译的每一个类。但一般来说,在编译器编译完第一个类后,CCL 会查找所有需要编译的类,然后编译它。为什么?Java 编译器类似于我们正在使用的规则:如果类不存在,或者与它的源码相比,它比较旧,那么它需要编译。其实,Java 编译器在 CCL 之前的一个步骤,它会做大部分的工作。
当 CCL 编译它们时,会报告它正在编译哪个应用程序上的类。在大多数的情况下,CCL 会在程序中的主类上调用编译器,它会做完所有要做的 -- 编译器的单一调用已足够了。
然而,有一种情形,在第一步时不会编译某些类。如果使用 Class.forName 方法,通过名称来装入类,Java 编译器会不知道这个类时所需要的。在这种情况下,您会看到 CCL 再次运行 Java 编译器来编译这个类。在源代码中演示了这个过程。
使用 CompilationClassLoader
要使用 CCL,必须以特殊方式调用程序。不能直接运行该程序,如: % java Foo arg1 arg2
应以下列方式运行它:
% java CCLRun Foo arg1 arg2
CCLRun 是一个特殊的存根程序,它创建 CompilingClassLoader 并用它来装入程序的主类,以确保通过 CompilingClassLoader 来装入整个程序。CCLRun 使用 Java Reflection API 来调用特定类的主方法并把参数传递给它。有关详细信息,请参阅源代码。
运行示例
源码包括了一组小类,它们演示了工作方式。主程序是 Foo 类,它创建类 Bar 的实例。类 Bar 创建另一个类 Baz 的实例,它在 baz 包内,这是为了展示 CCL 是如何处理子包里的代码。Bar 也是通过名称装入的,其名称为 Boo,这用来展示它也能与 CCL 工作。
每个类都声明已被装入并运行。现在用源代码来试一下。编译 CCLRun 和 CompilingClassLoader。确保不要编译其它类(Foo、Bar、Baz 和 Boo),否则将不会使用 CCL,因为这些类已经编译过了。
% java CCLRun Foo arg1 arg2
CCL: Compiling Foo.java
foo! arg1 arg2
bar! arg1 arg2
baz! arg1 arg2
CCL: Compiling Boo.java
Boo!
请注意,首先调用编译器,Foo.java 管理 Bar 和 baz.Baz。直到 Bar 通过名称来装入 Boo 时,被调用它,这时 CCL 会再次调用编译器来编译它。
CompilingClassLoader.java
以下是 CompilingClassLoader.java 的源代码
// $Id$
import java.io.*;
/*
A CompilingClassLoader compiles your Java source on-the-fly. It checks
for nonexistent .class files, or .class files that are older than their
corresponding source code.
*/
public class CompilingClassLoader extends ClassLoader
{
// Given a filename, read the entirety of that file from disk
// an