了解 Java ClassLoader
提交反馈意见 下一屏

主菜单

从这些链接中进行选择，开始教程。

1. 教程提示
2. 介绍
3. ClassLoader 结构
4. CompilingClassLoader
5. Java 2 中 ClassLoader 的变动
6. 源代码
7. 总结


章节索引：教程提示

主菜单 提交反馈意见 下一屏

教程提示
1. 我应该学习这门教程吗？
2. 获取帮助


章节索引：介绍

主菜单 提交反馈意见 下一屏

介绍
1. 什么是 ClassLoader?
2. 为什么编写 ClassLoader?
3. 定制 ClassLoader 示例
4. 我们的 ClassLoader 示例


章节索引：ClassLoader 结构

主菜单 提交反馈意见 下一屏

ClassLoader 结构
1. 概述
2. 方法 loadClass
3. 方法 defineClass
4. 方法 findSystemClass
5. 方法 resolveClass
6. 方法 findLoadedClass
7. 组装
8. 推想


章节索引：CompilingClassLoader

主菜单 提交反馈意见 下一屏

CompilingClassLoader
1. CCL 揭密
2. Java 编译的工作方式
3. 使用 CompilationClassLoader
4. 运行示例


章节索引：Java 2 中 ClassLoader 的变动

主菜单 提交反馈意见 下一屏

Java 2 中 ClassLoader 的变动
1. 概述
2. loadClass 的缺省实现
3. 新方法：findClass
4. 新方法：getSystemClassLoader
5. 新方法：getParent


章节索引：源代码

主菜单 提交反馈意见 下一屏

源代码
1. CompilingClassLoader.java
2. CCRun.java
3. Foo.java
4. Bar.java
5. baz/Baz.java
6. Boo.java


章节索引：总结

主菜单 提交反馈意见 下一屏

总结
1. 总结
2. 其它一些 ClassLoader 议题
3. 进一步了解和参考资料
4. 反馈意见


提交反馈意见 下一屏

      第一章：提示


教程提示

主菜单 章节菜单 提交反馈意见 下一屏
1. 教程提示

我应该学习这门教程吗？ 第 1 页（共 2 页）

Java ClassLoader 是一个重要的、但又常常被人忽略的 Java 运行时系统组件。它是负责在运行时查找和装入类文件的类。创建自己的 ClassLoader 可以以实用且有趣的方式定制 JVM，这样可以让您彻底重新定义如何将类文件引入系统。

本教程概述了 Java ClassLoader，并指导您构造在装入代码之前自动编译代码的示例 ClassLoader。您将完全了解 ClassLoader 的功能以及创建自己的 ClassLoader 需要执行的操作。

在学习本教程前，需了解一些 Java 编程的基本知识，包括创建、编译和执行简单的命令行 Java 程序的技能，以及掌握一些类文件范例的知识。

在学完本教程后，您会知道如何：

  • 扩展 JVM 的功能
  • 创建定制的 ClassLoader
  • 了解如何将定制的 ClassLoader 集成到 Java 应用程序
  • 修改您的 ClassLoader，从而能适合 Java 2 发行版
主菜单 章节菜单 提交反馈意见 下一屏


教程提示

主菜单 | 章节菜单 | 提交反馈意见 | 上一屏 |
 |  |  |  | 下一章
1. 教程提示

获取帮助 第 2 页（共 2 页）

如果对本教程的内容有什么疑问，可以通过 mito@panix.com 与作者 Greg Travis 联系。

Greg Travis 居住在纽约市，他是一位自由程序员。他对计算机的兴趣源自“无敌女金刚”的一段情节，剧中讲述了 Jamie 试图逃离一个照明和门窗由邪恶的人工智能控制的建筑物，此建筑物利用扬声器捉弄 Jamie。Greg 坚信，当计算机程序运行时，完全可能存在这种情形。

下一章
主菜单 章节菜单 提交反馈意见 上一屏


提交反馈意见 下一屏

      第二章：介绍


介绍

主菜单 章节菜单 提交反馈意见 下一屏
2. 介绍

什么是 ClassLoader? 第 1 页（共 4 页）

在流行的商业化编程语言中，Java 语言由于在 Java 虚拟机 (JVM) 上运行而显得与众不同。这意味着已编译的程序是一种特殊的、独立于平台的格式，并非依赖于它们所运行的机器。在很大程度上，这种格式不同于传统的可执行程序格式。

与 C 或 C++ 编写的程序不同，Java 程序并不是一个可执行文件，而是由许多独立的类文件组成，每一个文件对应于一个 Java 类。

此外，这些类文件并非立即全部都装入内存，而是根据程序需要装入内存。ClassLoader 是 JVM 中将类装入内存的那部分。

而且，Java ClassLoader 就是用 Java 语言编写的。这意味着创建您自己的 ClassLoader 非常容易，不必了解 JVM 的微小细节。

主菜单 章节菜单 提交反馈意见 下一屏


介绍

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
2. 介绍

为什么编写 ClassLoader? 第 2 页（共 4 页）

如果 JVM 已经有一个 ClassLoader，那么为什么还要编写另一个呢？问得好。缺省的 ClassLoader 只知道如何从本地文件系统装入类文件。不过这只适合于常规情况，即已全部编译完 Java 程序，并且计算机处于等待状态。

但 Java 语言最具新意的事就是 JVM 可以非常容易地从那些非本地硬盘或从网络上获取类。例如，浏览者可以使用定制的 ClassLoader 从 Web 站点装入可执行内容。

有许多其它方式可以获取类文件。除了简单地从本地或网络装入文件以外，可以使用定制的 ClassLoader 完成以下任务：

  • 在执行非置信代码之前，自动验证数字签名
  • 使用用户提供的密码透明地解密代码
  • 动态地创建符合用户特定需要的定制化构建类
任何您认为可以生成 Java 字节码的内容都可以集成到应用程序中。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


介绍

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
2. 介绍

定制 ClassLoader 示例 第 3 页（共 4 页）

如果使用过 JDK 或任何基于 Java 浏览器中的 Applet 查看器，那么您差不多肯定使用过定制的 ClassLoader。

Sun 最初发布 Java 语言时，其中最令人兴奋的一件事是观看这项新技术是如何执行在运行时从远程的 Web 服务器装入的代码。（此外，还有更令人兴奋的事 -- Java 技术提供了一种便于编写代码的强大语言。）更一些令人激动的是它可以执行从远程 Web 服务器通过 HTTP 连接发送过来的字节码。

此项功能归功于 Java 语言可以安装定制 ClassLoader。Applet 查看器包含一个 ClassLoader，它不在本地文件系统中寻找类，而是访问远程服务器上的 Web 站点，经过 HTTP 装入原始的字节码文件，并把它们转换成 JVM 内的类。

浏览器和 Applet 查看器中的 ClassLoaders 还可以做其它事情：它们支持安全性以及使不同的 Applet 在不同的页面上运行而互不干扰。

Luke Gorrie 编写的 Echidna 是一个开放源码包，它可以使您在单个虚拟机上运行多个 Java 应用程序。（请参阅进一步了解和参考资料。）它使用定制的 ClassLoader，通过向每个应用程序提供该类文件的自身副本，以防止应用程序互相干扰。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


介绍

主菜单 | 章节菜单 | 提交反馈意见 | 上一屏 |
 |  |  |  | 下一章
2. 介绍

我们的 ClassLoader 示例 第 4 页（共 4 页）

了解了 ClassLoader 如何工作以及如何编写 ClassLoader 之后，我们将创建称作 CompilingClassLoader (CCL) 的 Classloader。CCL 为我们编译 Java 代码，而无需要我们干涉这个过程。它基本上就类似于直接构建到运行时系统中的 "make" 程序。

注：进一步了解之前，应注意在 JDK 版本 1.2 中已改进了 ClassLoader 系统的某些方面（即 Java 2 平台）。本教程是按 JDK 版本 1.0 和 1.1 写的，但也可以在以后的版本中运行。

Java 2 中 ClassLoader 的变动描述了 Java 版本 1.2 中的变动，并提供了一些详细信息，以便修改 ClassLoader 来利用这些变动。

下一章节
主菜单 章节菜单 提交反馈意见 上一屏


提交反馈意见 下一屏

      第三章：ClassLoader 结构


ClassLoader 结构

主菜单 章节菜单 提交反馈意见 下一屏
3. ClassLoader 结构

概述 第 1 页（共 8 页）

ClassLoader 的基本目标是对类的请求提供服务。当 JVM 需要使用类时，它根据名称向 ClassLoader 请求这个类，然后 ClassLoader 试图返回一个表示这个类的 Class 对象。

通过覆盖对应于这个过程不同阶段的方法，可以创建定制的 ClassLoader。

在本章的其余部分，您会学习 Java ClassLoader 的关键方法。您将了解每一个方法的作用以及它是如何适合装入类文件这个过程的。您也会知道，创建自己的 ClassLoader 时，需要编写什么代码。

在下一章中，您将会利用这些知识来使用我们的 ClassLoader 示例 -- CompilingClassLoader。

主菜单 章节菜单 提交反馈意见 下一屏


ClassLoader 结构

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
3. ClassLoader 结构

方法 loadClass 第 2 页（共 8 页）

ClassLoader.loadClass() 是 ClassLoader 的入口点。其特征如下：

Class loadClass( String name, boolean resolve );

name 参数指定了 JVM 需要的类的名称，该名称以包表示法表示，如 Foo 或 java.lang.Object。

resolve 参数告诉方法是否需要解析类。在准备执行类之前，应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类，那么就不需要解析。

在 Java 版本 1.1 和以前的版本中，loadClass 方法是创建定制的 ClassLoader 时唯一需要覆盖的方法。（Java 2 中 ClassLoader 的变动提供了关于 Java 1.2 中 findClass() 方法的信息。）

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


ClassLoader 结构

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
3. ClassLoader 结构

方法 defineClass 第 3 页（共 8 页）

defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。

defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 -- 它把字节码分析成运行时数据结构、校验有效性等等。不必担心，您无需亲自编写它。事实上，即使您想要这么做也不能覆盖它，因为它已被标记成最终的。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


ClassLoader 结构

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
3. ClassLoader 结构

方法 findSystemClass 第 4 页（共 8 页）

findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件，如果存在，就使用 defineClass 将原始字节转换成 Class 对象，以将该文件转换成类。当运行 Java 应用程序时，这是 JVM 正常装入类的缺省机制。（Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。）

对于定制的 ClassLoader，只有在尝试其它方法装入类之后，再使用 findSystemClass。原因很简单：ClassLoader 是负责执行装入类的特殊步骤，不是负责所有类。例如，即使 ClassLoader 从远程的 Web 站点装入了某些类，仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的，所以要 JVM 以缺省方式装入它们：从本地文件系统。这就是 findSystemClass 的用途。

其工作流程如下：

  • 请求定制的 ClassLoader 装入类。
  • 检查远程 Web 站点，查看是否有所需要的类。
  • 如果有，那么好；抓取这个类，完成任务。
  • 如果没有，假定这个类是在基本 Java 库中，那么调用 findSystemClass，使它从文件系统装入该类。

在大多数定制 ClassLoaders 中，首先调用 findSystemClass 以节省在本地就可以装入的许多 Java 库类而要在远程 Web 站点上查找所花的时间。然而，正如，在下一章节所看到的，直到确信能自动编译我们的应用程序代码时，才让 JVM 从本地文件系统装入类。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


ClassLoader 结构

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
3. ClassLoader 结构

方法 resolveClass 第 5 页（共 8 页）

正如前面所提到的，可以不完全地（不带解析）装入类，也可以完全地（带解析）装入类。当编写我们自己的 loadClass 时，可以调用 resolveClass，这取决于 loadClass 的 resolve 参数的值。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


ClassLoader 结构

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
3. ClassLoader 结构

方法 findLoadedClass 第 6 页（共 8 页）

findLoadedClass 充当一个缓存：当请求 loadClass 装入类时，它调用该方法来查看 ClassLoader 是否已装入这个类，这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。
主菜单 章节菜单 提交反馈意见 上一屏 下一屏


ClassLoader 结构

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
3. ClassLoader 结构

组装 第 7 页（共 8 页）

让我们看一下如何组装所有方法。

我们的 loadClass 实现示例执行以下步骤。（这里，我们没有指定生成类文件是采用了哪种技术 -- 它可以是从 Net 上装入、或者从归档文件中提取、或者实时编译。无论是哪一种，那是种特殊的神奇方式，使我们获得了原始类文件字节。）

  • 调用 findLoadedClass 来查看是否存在已装入的类。

  • 如果没有，那么采用那种特殊的神奇方式来获取原始字节。

  • 如果已有原始字节，调用 defineClass 将它们转换成 Class 对象。

  • 如果没有原始字节，然后调用 findSystemClass 查看是否从本地文件系统获取类。

  • 如果 resolve 参数是 true，那么调用 resolveClass 解析 Class 对象。

  • 如果还没有类，返回 ClassNotFoundException。

  • 否则，将类返回给调用程序。
主菜单 章节菜单 提交反馈意见 上一屏 下一屏


ClassLoader 结构

主菜单 | 章节菜单 | 提交反馈意见 | 上一屏 |
 |  |  |  | 下一章
3. ClassLoader 结构

推想 第 8 页（共 8 页）

现在您已经了解了 ClassLoader 的工作原理，现在该构建一个了。在下一章中，我们将讨论 CCL。

下一章节
主菜单 章节菜单 提交反馈意见 上一屏


提交反馈意见 下一屏

      第四章：CompilingClassLoader


CompilingClassLoader

主菜单 章节菜单 提交反馈意见 下一屏
4. CompilingClassLoader

CCL 揭密 第 1 页（共 4 页）

我们的 ClassLoader (CCL) 的任务是确保代码被编译和更新。

下面描述了它的工作方式：

  • 当请求一个类时，先查看它是否在磁盘的当前目录或相应的子目录。

  • 如果该类不存在，但源码中有，那么调用 Java 编译器来生成类文件。

  • 如果该类已存在，检查它是否比源码旧。如果是，调用 Java 编译器来重新生成类文件。

  • 如果编译失败，或者由于其它原因不能从现有的源码中生成类文件，返回 ClassNotFoundException。

  • 如果仍然没有该类，也许它在其它库中，所以调用 findSystemClass 来寻找该类。

  • 如果还是没有，则返回 ClassNotFoundException。

  • 否则，返回该类。
主菜单 章节菜单 提交反馈意见 下一屏


CompilingClassLoader

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
4. CompilingClassLoader

Java 编译的工作方式 第 2 页（共 4 页）

在深入讨论之前，应该先退一步，讨论 Java 编译。通常，Java 编译器不只是编译您要求它编译的类。它还会编译其它类，如果这些类是您要求编译的类所需要的类。

CCL 逐个编译应用程序中的需要编译的每一个类。但一般来说，在编译器编译完第一个类后，CCL 会查找所有需要编译的类，然后编译它。为什么？Java 编译器类似于我们正在使用的规则：如果类不存在，或者与它的源码相比，它比较旧，那么它需要编译。其实，Java 编译器在 CCL 之前的一个步骤，它会做大部分的工作。

当 CCL 编译它们时，会报告它正在编译哪个应用程序上的类。在大多数的情况下，CCL 会在程序中的主类上调用编译器，它会做完所有要做的 -- 编译器的单一调用已足够了。

然而，有一种情形，在第一步时不会编译某些类。如果使用 Class.forName 方法，通过名称来装入类，Java 编译器会不知道这个类时所需要的。在这种情况下，您会看到 CCL 再次运行 Java 编译器来编译这个类。在源代码中演示了这个过程。

主菜单 上一屏 下一屏


CompilingClassLoader

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
4. CompilingClassLoader

使用 CompilationClassLoader 第 3 页（共 4 页）

要使用 CCL，必须以特殊方式调用程序。不能直接运行该程序，如：

% java Foo arg1 arg2

应以下列方式运行它：

% java CCLRun Foo arg1 arg2

CCLRun 是一个特殊的存根程序，它创建 CompilingClassLoader 并用它来装入程序的主类，以确保通过 CompilingClassLoader 来装入整个程序。CCLRun 使用 Java Reflection API 来调用特定类的主方法并把参数传递给它。有关详细信息，请参阅源代码。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


CompilingClassLoader

主菜单 | 章节菜单 | 提交反馈意见 | 上一屏 |
 |  |  |  | 下一章
4. CompilingClassLoader

运行示例 第 4 页（共 4 页）

源码包括了一组小类，它们演示了工作方式。主程序是 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 会再次调用编译器来编译它。

下一章节
主菜单 章节菜单 提交反馈意见 上一屏


提交反馈意见 下一屏

      第五章：Java 中 ClassLoader 的变动


Java 2 中 ClassLoader 的变动

主菜单 章节菜单 提交反馈意见 下一屏
5. Java 2 中 ClassLoader 的变动

概述 第 1 页（共 5 页）

在 Java 版本 1.2 和以后的版本中，对 ClassLoader 做了一些改进。任何为老系统编写的代码可以在新版本中运行，但新系统为您提供了一些便利。

新模型是委托模型，这意味着如果 ClassLoader 不能找到类，它会请求父代 ClassLoader 来执行此项任务。所有 ClassLoaders 的根是系统 ClassLoader，它会以缺省方式装入类 -- 即，从本地文件系统。

主菜单 章节菜单 提交反馈意见 下一屏


Java 2 中 ClassLoader 的变动

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
5. Java 2 中 ClassLoader 的变动

loadClass 的缺省实现 第 2 页（共 5 页）

定制编写的 loadClass 方法一般尝试几种方式来装入所请求的类，如果您编写许多类，会发现一次次地在相同的、很复杂的方法上编写变量。

在 Java 1.2 中 loadClass 的实现嵌入了大多数查找类的一般方法，并使您通过覆盖 findClass 方法来定制它，在适当的时候 findClass 会调用 loadClass。

这种方式的好处是您可能不一定要覆盖 loadClass；只要覆盖 findClass 就行了，这减少了工作量。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


Java 2 中 ClassLoader 的变动

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
5. Java 2 中 ClassLoader 的变动

新方法：findClass 第 3 页（共 5 页）

loadClass 的缺省实现调用这个新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代码，而无需要复制其它代码（例如，当专门的方法失败时，调用系统 ClassLoader）。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


Java 2 中 ClassLoader 的变动

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
5. Java 2 中 ClassLoader 的变动

新方法：getSystemClassLoader 第 4 页（共 5 页）

如果覆盖 findClass 或 loadClass，getSystemClassLoader 使您能以实际 ClassLoader 对象来访问系统 ClassLoader（而不是固定的从 findSystemClass 调用它）。

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


Java 2 中 ClassLoader 的变动

主菜单 | 章节菜单 | 提交反馈意见 | 上一屏 |
 |  |  |  | 下一章
5. Java 2 中 ClassLoader 的变动

新方法：getParent 第 5 页（共 5 页）

为了将类请求委托给父代 ClassLoader，这个新方法允许 ClassLoader 获取它的父代 ClassLoader。当使用特殊方法，定制的 ClassLoader 不能找到类时，可以使用这种方法。

父代 ClassLoader 被定义成创建该 ClassLoader 所包含代码的对象的 ClassLoader。

下一章节
主菜单 章节菜单 提交反馈意见 上一屏


提交反馈意见 下一屏

      第六章：源代码


源代码

主菜单 章节菜单 提交反馈意见 下一屏
6. 源代码

CompilingClassLoader.java 第 1 页（共 6 页）

以下是 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
  // and return it as a byte array.
  private byte[] getBytes( String filename ) throws IOException {
    // Find out the length of the file
    File file = new File( filename );
    long len = file.length();

    // Create an array that's just the right size for the file's
    // contents
    byte raw[] = new byte[(int)len];

    // Open the file
    FileInputStream fin = new FileInputStream( file );

    // Read all of it into the array; if we don't get all,
    // then it's an error.
    int r = fin.read( raw );
    if (r != len)
      throw new IOException( "Can't read all, "+r+" != "+len );

    // Don't forget to close the file!
    fin.close();

    // And finally return the file contents as an array
    return raw;
  }

  // Spawn a process to compile the java source code file
  // specified in the 'javaFile' parameter.  Return a true if
  // the compilation worked, false otherwise.
  private boolean compile( String javaFile ) throws IOException {
    // Let the user know what's going on
    System.out.println( "CCL: Compiling "+javaFile+"..." );

    // Start up the compiler
    Process p = Runtime.getRuntime().exec( "javac "+javaFile );

    // Wait for it to finish running
    try {
      p.waitFor();
    } catch( InterruptedException ie ) { System.out.println( ie ); }

    // Check the return code, in case of a compilation error
    int ret = p.exitValue();

    // Tell whether the compilation worked
    return ret==0;
  }

  // The heart of the ClassLoader -- automatically compile
  // source as necessary when looking for class files
  public Class loadClass( String name, boolean resolve )
      throws ClassNotFoundException {

    // Our goal is to get a Class object
    Class clas = null;

    // First, see if we've already dealt with this one
    clas = findLoadedClass( name );

    //System.out.println( "findLoadedClass: "+clas );

    // Create a pathname from the class name
    // E.g. java.lang.Object => java/lang/Object
    String fileStub = name.replace( '.', '/' );

    // Build objects pointing to the source code (.java) and object
    // code (.class)
    String javaFilename = fileStub+".java";
    String classFilename = fileStub+".class";

    File javaFile = new File( javaFilename );
    File classFile = new File( classFilename );

    //System.out.println( "j "+javaFile.lastModified()+" c "+
    //  classFile.lastModified() );

    // First, see if we want to try compiling.  We do if (a) there
    // is source code, and either (b0) there is no object code,
    // or (b1) there is object code, but it's older than the source
    if (javaFile.exists() &&
         (!classFile.exists() ||
          javaFile.lastModified() > classFile.lastModified())) {

      try {
        // Try to compile it.  If this doesn't work, then
        // we must declare failure.  (It's not good enough to use
        // and already-existing, but out-of-date, classfile)
        if (!compile( javaFilename ) || !classFile.exists()) {
          throw new ClassNotFoundException( "Compile failed: "+javaFilename );
        }
      } catch( IOException ie ) {

        // Another place where we might come to if we fail
        // to compile
        throw new ClassNotFoundException( ie.toString() );
      }
    }

    // Let's try to load up the raw bytes, assuming they were
    // properly compiled, or didn't need to be compiled
    try {

      // read the bytes
      byte raw[] = getBytes( classFilename );

      // try to turn them into a class
      clas = defineClass( name, raw, 0, raw.length );
    } catch( IOException ie ) {
      // This is not a failure!  If we reach here, it might
      // mean that we are dealing with a class in a library,
      // such as java.lang.Object
    }

    //System.out.println( "defineClass: "+clas );

    // Maybe the class is in a library -- try loading
    // the normal way
    if (clas==null) {
      clas = findSystemClass( name );
    }

    //System.out.println( "findSystemClass: "+clas );

    // Resolve the class, if any, but only if the "resolve"
    // flag is set to true
    if (resolve && clas != null)
      resolveClass( clas );

    // If we still don't have a class, it's an error
    if (clas == null)
      throw new ClassNotFoundException( name );

    // Otherwise, return the class
    return clas;
  }
}

主菜单 章节菜单 提交反馈意见 下一屏


源代码

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
6. 源代码

CCRun.java 第 2 页（共 6 页）

以下是 CCRun.java 的源代码

// $Id$

import java.lang.reflect.*;

/*

CCLRun executes a Java program by loading it through a
CompilingClassLoader.

*/

public class CCLRun
{
  static public void main( String args[] ) throws Exception {

    // The first argument is the Java program (class) the user
    // wants to run
    String progClass = args[0];

    // And the arguments to that program are just
    // arguments 1..n, so separate those out into
    // their own array
    String progArgs[] = new String[args.length-1];
    System.arraycopy( args, 1, progArgs, 0, progArgs.length );

    // Create a CompilingClassLoader
    CompilingClassLoader ccl = new CompilingClassLoader();

    // Load the main class through our CCL
    Class clas = ccl.loadClass( progClass );

    // Use reflection to call its main() method, and to
    // pass the arguments in.

    // Get a class representing the type of the main method's argument
    Class mainArgType[] = { (new String[0]).getClass() };

    // Find the standard main method in the class
    Method main = clas.getMethod( "main", mainArgType );

    // Create a list containing the arguments -- in this case,
    // an array of strings
    Object argsArray[] = { progArgs };

    // Call the method
    main.invoke( null, argsArray );
  }
}

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


源代码

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
6. 源代码

Foo.java 第 3 页（共 6 页）

以下是 Foo.java 的源代码

// $Id$

public class Foo
{
  static public void main( String args[] ) throws Exception {
    System.out.println( "foo! "+args[0]+" "+args[1] );
    new Bar( args[0], args[1] );
  }
}

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


源代码

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
6. 源代码

Bar.java 第 4 页（共 6 页）

以下是 Bar.java 的源代码

// $Id$

import baz.*;

public class Bar
{
  public Bar( String a, String b ) {
    System.out.println( "bar! "+a+" "+b );
    new Baz( a, b );

    try {
      Class booClass = Class.forName( "Boo" );
      Object boo = booClass.newInstance();
    } catch( Exception e ) {
      e.printStackTrace();
    }
  }
}

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


源代码

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
6. 源代码

baz/Baz.java 第 5 页（共 6 页）

以下是 baz/Baz.java 的源代码

// $Id$

package baz;

public class Baz
{
  public Baz( String a, String b ) {
    System.out.println( "baz! "+a+" "+b );
  }
}

主菜单 章节菜单 提交反馈意见 上一屏 下一屏


源代码

主菜单 | 章节菜单 | 提交反馈意见 | 上一屏 |
 |  |  |  | 下一章
6. 源代码

Boo.java 第 6 页（共 6 页）

以下是 Boo.java 的源代码

// $Id$

public class Boo
{
  public Boo() {
    System.out.println( "Boo!" );
  }
}

下一章节
主菜单 章节菜单 提交反馈意见 上一屏


提交反馈意见 下一屏

      第七章：总结


总结

主菜单 章节菜单 提交反馈意见 下一屏
7. 总结

总结 第 1 页（共 4 页）

当学完这篇短小的教程后，您知道了如何创建定制的 ClassLoader，这有助于了解 JVM 的核心。能够从任何位置装入类文件，或者甚至是实时生成它们，这可以扩展 JVM 的范围并使您能做一些有趣且功能强大的事情。

主菜单 章节菜单 提交反馈意见 下一屏


总结

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
7. 总结

其它一些 ClassLoader 议题 第 2 页（共 4 页）

正如在本教程前面所提到的，定制 ClassLoaders 对类似于基于 Java 的浏览器和小应用程序查看器这样的程序是很重要的。这里有一些关于有趣的 ClassLoaders 的议题：

  • 安全性。在类传递给 JVM 之前，ClassLoader 可以检查这些类，来查看它们是否有正确的数字签名。也可以创建一种“沙箱”，它可以通过检查源代码和拒绝类（这些类试图在沙箱之外做一些事情）来不允许某一种方法调用。

  • 加密。 创建一个 ClassLoader，它可以在网上解密，这是有可能的，从而使有反编译器的某些人不能读取您磁盘上的类文件。用户必须用解密代码的密码来运行程序。

  • 归档。想要以特殊格式或带特殊压缩来分发您的代码吗？ClassLoader 可以从任何它想要的源码中提取原始类文件。

  • 自展开程序。把 Java 应用程序编译成单个可执行类文件，这个文件包含已压缩的和／或已加密的类文件数据，同时有一个固有的 ClassLoader，这是有可能的；当程序运行时，它会在内存中完全自行解开 -- 不需要先安装。

  • 动态生成。这里没有限制。您可以生成引用其它还未生成类的类 -- 实时创建整个类并可在任何时刻引入 JVM。
主菜单 章节菜单 提交反馈意见 上一屏 下一屏


总结

主菜单 章节菜单 提交反馈意见 上一屏 下一屏
7. 总结

进一步了解和参考资料 第 3 页（共 4 页）

下面一些参考资料可以帮助您理解 ClassLoaders：

  • 阅读 Sun 网站上的在线文档。

  • Java Developer Connection 有一个定制 ClassLoader 教程。

  • 学习 Java 语言规范中的类文件装入。

  • Java 虚拟机规范包含了 ClassLoaders 的信息。

  • JDK 版本 1.3 文档有使用 ClassLoader 的 核心 Java 库类的列表。

  • Luke Gorrie 撰写的 Echidna 是一段依靠定制 ClassLoader、非常有用的软件示例。

  • “创建 Java 1.2 样式的定制 ClassLoader”（JavaWorld，2000 年 3 月）提出了一些关于在 JDK 1.2 下构建 ClassLoader 的观点。

  • “从 XML 数据生成类”（developerworks，2000 年 8 月）描述了使用定制 ClassLoader 来实时创建新类。

  • 如果您是 Java 平台的新手，Java 语言基础（developerworks，2000 年 11 月）提供了关于该平台一些基础知识的指南。
主菜单 章节菜单 提交反馈意见 上一屏 下一屏


总结

主菜单 章节菜单 提交反馈意见 上一屏
7. 总结

反馈意见 第 4 页（共 4 页）

请告诉我们这篇教程对您是否有所帮助，同时也欢迎您提出一些宝贵的意见和建议以便我们今后做得更好一些。如果您还想我们提供一些别的教程，不妨与我们联系。非常感谢！

对于本教程的内容有什么疑题，可以通过 mito@panix.com 与作者 Greg Travis 联系。

您认为这个教程怎么样？

真棒！(5) 好教程 (4) 一般; 尚可 (3) 需提高 (2) 太差！(1)

意见？
主菜单 章节菜单 提交反馈意见 上一屏