摘要 通过构建一个能够把java类装载隔离到一个指定的jar文件中的类装载组件容器框架,你可以确保运行时刻会装载你期望的组件版本。
Java的类装载框架强有力且具有灵活性。它答应应用程序存取类库而不必链接到静态的"include"文件。代之的是,它能够从指定位置装载包含库类和资源的档案文件,例如由CLASSPATH环境变量所定义的目录和网络位置。由系统来动态地解析对类和资源的运行时刻参考,从而简化了更新和版本发行。然而,每一个库都有其自己的依靠性集合-并且由开发者和发布人员来保证他们的应用程序适当地参考正确的版本。遗憾的是,默认的类装载系统和特定依靠性的结合可能并且确实会导致错误、系统崩溃甚至于更糟糕的情况发生。
本文中,我将向你建议一个实现类装载的容器框架,从而解决这些问题。
一、 Java Classpath
Java根据环境属性/变量CLASSPATH来指定运行时刻用来查找类和其它资源的路径。你可以通过设置CLASSPATH环境变量或使用Java命令行选项--classpath来定义CLASSPATH属性。
典型地,一个Java运行时刻以下面顺序查找和加载类:
1. 在bootstrap类列表中的类-这些是体现Java平台的类,例如在rt.jar中的类。
2. 出现在扩展类列表中的类-这些类使用扩展机制框架来扩展Java平台,使用位于运行时刻环境的/lib/ext目录下的档案文件(.jar,.zip,等等。)。
3. 用户类-这些类不使用-classpath命令行选项或CLASSPATH环境变量标识的扩展机制架构。
二、 档案与Classpath
一个档案.jar或.zip文件可以包括一个manifest文件-它们包含能够用于提供档案信息,设置档案属性,等等的入口。这个manifest文件还可以通过包括一个名为Class-Path的入口(它包含一个档案和目录列表)来扩展classpath。JDK 1.3中引入了Class-Path manifest入口用于指定可选的据需要可以加载的jar文件和目录。下面是一个Class-Path入口的例子:
Class-Path: mystuff/utils.jar
mystuff/logging.jar mylib/
Java提供了一种可扩展模型用于指定装载类的位置和文件列表。然而,由此也引发了一些问题,例如,一个不同版本的库可能存在于classpath中-这超出一个执行类所期望的结果。
三、 Classpath版本冲突
在Java中,一个类的运行时刻标识是由通过其完全限定名字来定义的(在类名之前的包名,有时被作为FQN),所有这些都添加到装载类的相关装载器的ID。这样以来,由多个类加载器加载的一个类的每一个实例都将被当作是Java运行时刻的一个单独的实体。这意味着,运行时刻能够在任何时间装载同一个类的多个版本。这是一种非常有力和相当灵活的特征;然而,假如一位开发人员不认真地使用的话,某些副作用可能会令他迷惑不解。
可以设想,你在开发一个企业应用程序-它使用类似语义从多种源存取数据,例如一个文件系统和一个数据库。许多这种类型的系统都暴露一个数据存取层-通过抽象类似数据源的数据存取对象(DAO)。现在,设想你装载一个新版本的一个数据库DAO,使用一种略微不同的API来满足一个DAO客户端的新特征的要求-但是你仍然需要旧式的DAO以便适合于其它还没有为这种新的API预备好的客户端。在典型的运行时刻环境下,这种新的DAO将简单地替换旧的版本并且所有的新实例都将从新版本中创建。然而,假如在不停止运行时刻环境的前提下发生更新,那么任何已经存在的旧DAO的实例将与该新DAO的任何实例一起驻留于内存中-当创建这些新实例时。这已经足已令人迷惑了。更为糟糕的是,一位DAO客户期望创建一个旧版本的DAO的实例,但是实际上得到一个具有已改变的API的新版本的实例。正如你所见,这可能会带来一些有趣的挑战。
为了确保稳定性和安全性,调用代码必须能够指明它想使用的类的正确版本。为此,你可以创建一个类加载器,组件容器模型并且使用一些简单的类加载技术。
四、 档案与组件
因为档案文件(jar文件,zip文件,等等)与Java类加载机制和发布工具之间具有相当松的耦合性,所以它们是一种用作自定义组件容器的自然的候选。一个Java组件在一个档案文件中的打包与发布的成功依靠于:
· 能够指定要实例化一个组件的哪个版本的开发者
· 装载组件的辅助类的正确版本-根据与该组件在同一个jar文件中发现的信息。
这使得组件的开发者和消费者能够完全控制实际创建和使用每一个组件的相应版本。
在下面的几节中,我将讨论一下有关于定义组件和组件命名空间的概念。
五、 共享辅助资源
最大的问题之一是,当使用标准类加载器在Java中处理共享库时,所有的类都被加载到一个命名空间中。这使得在任何给定时刻很难使用相同库的不同版本。你所需要的是,一个组件能够定义它自己的命名空间-该组件及其所有辅助库将会装载到其中的。
因为在Java中,一个类的运行时刻标识是使用类的完全限定名和其加载器的ID来定义的,所以一个命名空间已经相应于每一个类加载器存在。因此,你可以使用类加载器来构建一个组件容器,由它来定义一个组件及其依靠对象的一个命名空间。
例如,假如我有一个命名为"com.jeffhanson.components.HelloWorld"的类,我想运行它的两个版本,那么解决方案是,使用一个类装载器创建HelloWorld类的一个版本的一个实例,而使用另一个类装载器创建另一个版本的HelloWorld类。图1展示了这一概念。
图1.使用多个类装载器:由于Java命名惯例特征的影响,使用不同的类装载器将定义不同的命名空间。
正如我将在本文中所要展示的,使用两个不同的类装载器来实例化一个类的技术实际上创建了一个虚拟的命名空间。然而,我实际上刚好创建了同一个版本的类的多个实例。
为了便于加载和实例化同一个类的多个版本,我将展示(在下面的几节中)一个组件-容器框架-它基于类装载器命名空间机制以答应装载同一个类的不同版本。六、 利用Classloader命名空间
你可以把组件容器框架实现为一个容器实体-负责加载在jar或zip档案中定义的组件以及该组件需要的辅助类。这个框架的创建目标是:
1. 答应开发者指定实例化一个组件的哪个版本。
2. 基于与组件在同一个jar文件中找到的信息为每个组件装载正确的辅助类。
3. 跨组件共享辅助类和档案。
你将需要一个配置文件来定义组件及其相应的辅助文件,正如下列示例所展示的: