Chinaunix首页 | 论坛 | 博客
  • 博客访问: 133570
  • 博文数量: 69
  • 博客积分: 2895
  • 博客等级: 少校
  • 技术积分: 710
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-03 18:05
文章分类

全部博文(69)

文章存档

2010年(69)

我的朋友

分类:

2010-09-11 16:15:58

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

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

在学习本教程前,需了解一些 Java 编程的基本知识,包括创建、编译和执行简单的命令行 Java 程序的技能,以及掌握一些类文件范例的知识。
在学完本教程后,您会知道如何:
* 扩展 JVM 的功能
* 创建定制的 ClassLoader
* 了解如何将定制的 ClassLoader 集成到 Java 应用程序
* 修改您的 ClassLoader,从而能适合 Java 2 发行版

什么是 ClassLoader?
在流行的商业化编程语言中,Java 语言由于在 Java 虚拟机 (JVM) 上运行而显得与众不同。这意味着已编译的程序是一种特殊的、独立于平台的格式,并非依赖于它们所运行的机器。在很大程度上,这种格式不同于传统的可执行程序格式。
与 C 或 C++ 编写的程序不同,Java 程序并不是一个可执行文件,而是由许多独立的类文件组成,每一个文件对应于一个 Java 类。
此外,这些类文件并非立即全部都装入内存,而是根据程序需要装入内存。ClassLoader 是 JVM 中将类装入内存的那部分。
而且,Java ClassLoader 就是用 Java 语言编写的。这意味着创建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小细节。
为什么编写 ClassLoader? 第 2 页(共 4 页)
如果 JVM 已经有一个 ClassLoader,那么为什么还要编写另一个呢?问得好。缺省的 ClassLoader 只知道如何从本地文件系统装入类文件。不过这只适合于常规情况,即已全部编译完 Java 程序,并且计算机处于等待状态。
但 Java 语言最具新意的事就是 JVM 可以非常容易地从那些非本地硬盘或从网络上获取类。例如,浏览者可以使用定制的 ClassLoader 从 Web 站点装入可执行内容。
有许多其它方式可以获取类文件。除了简单地从本地或网络装入文件以外,可以使用定制的 ClassLoader 完成以下任务:
* 在执行非置信代码之前,自动验证数字签名
* 使用用户提供的密码透明地解密代码
* 动态地创建符合用户特定需要的定制化构建类
任何您认为可以生成 Java 字节码的内容都可以集成到应用程序中。
定制 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,通过向每个应用程序提供该类文件的自身副本,以防止应用程序互相干扰。
我们的 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 的基本目标是对类的请求提供服务。当 JVM 需要使用类时,它根据名称向 ClassLoader 请求这个类,然后 ClassLoader 试图返回一个表示这个类的 Class 对象。
通过覆盖对应于这个过程不同阶段的方法,可以创建定制的 ClassLoader。
在本章的其余部分,您会学习 Java ClassLoader 的关键方法。您将了解每一个方法的作用以及它是如何适合装入类文件这个过程的。您也会知道,创建自己的 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() 方法的信息。)
方法 defineClass
defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。
defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 — 它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成最终的。
方法 findSystemClass
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 从本地文件系统装入类。
方法 resolveClass
正如前面所提到的,可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。
方法 findLoadedClass
findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。
组装
让我们看一下如何组装所有方法。
我们的 loadClass 实现示例执行以下步骤。(这里,我们没有指定生成类文件是采用了哪种技术 — 它可以是从 Net 上装入、或者从归档文件中提取、或者实时编译。无论是哪一种,那是种特殊的神奇方式,使我们获得了原始类文件字节。)
* 调用 findLoadedClass 来查看是否存在已装入的类。
* 如果没有,那么采用那种特殊的神奇方式来获取原始字节。
* 如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
* 如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
* 如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
* 如果还没有类,返回 ClassNotFoundException。
* 否则,将类返回给调用程序。
CCL 揭密
我们的 ClassLoader (CCL) 的任务是确保代码被编译和更新。
下面描述了它的工作方式:
* 当请求一个类时,先查看它是否在磁盘的当前目录或相应的子目录。
* 如果该类不存在,但源码中有,那么调用 Java 编译器来生成类文件。
* 如果该类已存在,检查它是否比源码旧。如果是,调用 Java 编译器来重新生成类文件。
* 如果编译失败,或者由于其它原因不能从现有的源码中生成类文件,返回 ClassNotFoundException。
* 如果仍然没有该类,也许它在其它库中,所以调用 findSystemClass 来寻找该类。
* 如果还是没有,则返回 ClassNotFoundException。
* 否则,返回该类。
Java 编译的工作方式 第 2 页(共 4 页)
在深入讨论之前,应该先退一步,讨论 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 来调用特定类的主方法并把参数传递给它。有关详细信息,请参阅源代码。
运行示例 第 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 会再次调用编译器来编译它。

阅读(630) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~