方法 findLoadedClass
findLoadedClass实现一个缓存:当要求loadClass来加载一个类的时候,可以先调用这个方法看看这个类是否已经被加载,防止重新加载一个已经被加载的类。这个方法必须先被调用,我们看一下这些方法是如何组织在一起的。
我们的例子实现loadClass执行以下的步骤。(我们不指定通过某种具体的技术获得类文件,-它可能从网络,从压缩包或者动态编译的。无论如何,我们获得的是原始字节码文件)
* 调用findLoadedClass检查这个类是否已经加载。
* 如果没有加载,我们通过某种方式获得原始字节数组。
* 假如已经获得该数组,调用defineClass把它转化成类对象。
* 如果无法获得该原始字节数组,调用findSystemClass 检查是否可以从本地文件系统中记载。
* 如果参数resolve为true,调用resolveClass来解析类对象。
* 如果还没有找到类,抛出一个ClassNotFoundException异常。
* 否则,返回这个类。
现在我们对类加载器的应用知识有个较全面的了解,可以创建自定义类加载器了。在下一部分,我们将讨论CCL。
第四部分. CompilingClassLoader
CCL给我们展示了类加载器的功能, CCL的目的是让我们的代码能够自动编译和更新。下面描述它是怎么工作的:
* 当有一个类的请求时,先检查磁盘的当前目录和子目录上是否存在这个类文件。
* 如果没有类文件,但是却有源代码文件,调用Java编译器编译生成类文件。
* 如果类文件已经存在,检查该类文件是否比源代码文件陈旧。如果类文件比源代码文件陈旧,调用Java编译器重新生成类文件。
* 如果编译失败,或者由于其他原因导致无法从源文件生成类文件,抛出异常ClassNotFoundException。
* 如果还没有获得这个类,可能存在其他的类库里,调用findSystemClass看是否能找到。
* 如果没有找到,抛出异常ClassNotFoundException。
* 否则,返回该类。
Java编译是如何实现的?
在我们进一步讨论前,我们需要先弄清楚Java的编译过程。通常,Java编译器不仅仅编译指定的那些类。如果指定的那些类需要的话,它还会编译其它的一些相关类。CCL会一个一个的编译我们在应用程序中需要编译的那些类。不过,一般来说,编译器编译完第一个类后,CCL将会发现其实其他需要的相关类已经被编译了。为什么呢?Java编译器使用我们差不多的规则:如果一个了类不存在或者源文件已经被更新,就会编译这个类。Java编译器基本上比 CCL早了一步,大部分工作都被Java编译器完成了。我们看起来就像是CCL在编译这些类。
在大多数情况下,你将发现它是在主函数类中调用编译器,就仅仅这些而已--简单的一个调用就够了。不过有一种特殊情况,这些类在第一次出现的时候不编译。如果你根据类名加载一个类,使用方法Class.forName,Java编译器并不知道是否需要这个类。在这种情况下,你发现CCL再次调用编译器来编译该类。第六部分的代码说明了这个过程。
使用CompilationClassLoader
为了使用CCL,我们不能直接运行我们的程序,必须以一种特殊的方式运行,就像这样:
% java Foo arg1 arg2
我们这样运行它:
% java CCLRun Foo arg1 arg2
CCLRun是一个特殊的存根程序,它来创建CompilingClassLoader 并且用它来加载我们的主函数类,这样可以确保所有的整个程序都是由CompilingClassLoader加载的。CCLRun利用Java反射API 来调用主函数类的主函数并且给这个函数传递参数。想了解更多,参考第六部分的源代码。
运行示例
我们演示一下整个过程式怎么工作的。
主程序是一个叫做Foo的类,它创建一个类Bar的实例。这个Bar实例又创建一个类Baz的实例,类Baz存在于包baz中,这是为了演示 CCL如何从子包中加载类。Bar还根据类名加载类Boo,这个也是CCL完成的。所有的类都加载了就可以运行了。利用第六章的源代码来执行这个程序。编译CCLRun和CompilingClassLoader。确保你没有编译其它的类(Foo, Bar, Baz, and 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一起编译了俄。而类Boo直道需要加载的时候,CCL才再次调用编译器来编译它