Java 平台的扩展机制
2008-12-19
作者:王秀峰,李利
一、什么是扩展机制?
从1.2版开始,Java 平台引入了一种机制——扩展(Extension),这种机制提供了一种标准的、方便的方式,以使在同一Java平台上运行的应用程序" title="应用程序">应用程序都能使用客户提供的(非平台本身提供的)API。通过扩展机制,可以使用一些自己所写的包和类对Java平台进行增强,我们暂且称这些类或包为“扩展”。采用扩展机制,不用在类路径(classpath)上添加“扩展”,运行环境也能找到并加载" title="加载">加载这些“扩展” 。从这一点看,“扩展”就像Java平台的核心类一样,这也正是这些类之所以称为“扩展”的原因——它们实际上是对Java平台核心API进行了扩展,更加加强了“Write Once,Run Anywhere”的理念。
如图1所示,“扩展”就是将类打包成“JAR”文件,成为Java平台的可添加的模块,它们的类和公共的API对于运行在Java平台上的任何应用程序都是可以使用的。不但如此,Java的扩展机制还提供了一种通过远程下载供Applet使用“扩展”的方式。
二、扩展的方式
有两种扩展方式,分别适于不同的环境,下面我们通过简单示例说明如何应用Java平台的扩展机制。
方式一:安装扩展
“安装扩展”是指将“扩展”的JAR文件放在Java运行环境(JRE)软件安装" title="软件安装">软件安装目录中的/lib/ext目录下(注意:是JRE软件安装目录下的/lib/ext/目录)。JRE是Java开发工具包(Java Development Kit,JDK)的运行部分,JRE既可以单独使用,也可以作为JDK的一部分而使用(如果只是提供运行环境,而不是用以开发,仅仅安装JRE就可以了)。JDK软件的目录树如下所示:
JRE就是上图中灰色部分,它是JDK的真子集。不论JRE是单独使用,还是作为JDK的一部分,在JRE中的 /lib/ext目录下的任何JAR文件都自动作为运行环境的“扩展”。
例如,我们创建一个简单的“扩展”,含有一个类Square,用于计算一个整数的平方。代码如下:
public final class Square{
public static int getSquare(int a){
return a*a;
}
}
Square 类含有一个方法——getSquare,它以一个整数为参数,返回这个整数的平方。
假使有一个应用程序(Application)——ComputeSquareApp,要使用Square类,代码如下:
public class ComputeSquareApp{
public static void main(String[] args){
int s=10;
System.out.println('整数'+s+'的平方是'+Square.getSquare(s));
}
}
假如我们已将Square类打包成square.jar文件,那么在不使用扩展机制的情况下怎么运行ComputeSquareApp这个应用程序呢?因为Square类不是Java平台的一部分(是我们自己定义的类),所以,如果square.jar是在目录 c:myjava下,为了正常运行ComputeSquareApp,则应当使用如下命令:
java –classpath .;c:myjavasquare.jar ComputeSq
也就是命令中的类路径既要包含ComputeSquareApp.class文件所在的当前目录,又要包含square.jar的路径。
下面我们看一下在采用扩展机制时如何运行ComputeSquareApp程序。要使Square类成为“扩展”,需要将square.jar文件放在JRE的 lib/ext目录下,这样,就使Square类自动成为安装方式" title="安装方式">安装方式的“扩展”。因为我们将square.jar作为安装方式的“扩展”,运行环境就能够找到并加载Square类,即使我们不指定Square的类路径。这样,我们在命令行直接输入下面不带类路径的命令,就能运行程序:
java ComputeSquareApp
同样,在这个做了扩展的系统下运行的任何Applet或Application都可以找到并使用Square类。
方式二:“下载扩展”
“下载扩展”是指在其它JAR文件的清单(manifest)文件的Class-Path头中列出的JAR文件中的类,“下载扩展”不像“安装扩展”那样处于JRE中,它仅是在需要时从所指定的URL加载。例如,a.jar和b.jar是两个在同一目录下的JAR文件,a.jar的manifest文件包含下面的" title="面的">面的头:
Class-Path:b.jar
那么b.jar中的类就可用作a.jar中类的“下载扩展”,这样b.jar中的类不用写在类路径上,a.jar中的类就可以调用b.jar中的类(a.jar自身可以是也可以不是“扩展”)。
为了更好理解“下载扩展”,让我们看一个例子。
假如我们创建了一个要使用前面Square类的Applet——ComputeSquareApplet:
import java.applet.Applet;
import java.awt.*;
public class ComputeSquareApplet extends Applet {
int s=10;
public void paint(Graphics g) {
g.drawString('整数'+s+'的平房是'
+ Square.getSquare(s), 10, 10);
}
}
这个Applet通过调用Square类的方法getSquare计算一个整数的平方。然而,这个Applet是下载到调用方的机器上运行的,调用方的系统中并没有Square类,所以若不采取措施ComputeSquareApplet是不能正常运行的。解决这个问题的方式之一就是将Square类做成“下载扩展”,在ComputeSquareApplet运行时可以加载“下载扩展”。
如何做呢?首先将Square类打包成Square.jar文件,将ComputeSquareApplet打包成ComputeSquareApplet.jar文件(须将Square.jar列到ComputeSquareApplet.jar的manifest文件头部的Class-Path中),这样Square类就会被当做“下载扩展” 。ComputeSquareApplet.jar的manifest文件就像下面这样:
Manifest-Version: 1.0
Class-Path: RectangleArea.jar
上面ComputeSquareApplet.jar的manifest文件的Class-Path没有特别指明路径,表示Square.jar和ComputeSquareApplet.jar处于同一个目录中。
另外,如果Applet或Application使用了不止一个扩展,我们可以在manifest文件中列出多个URL,例如,下面就是一个有效的Class-Path头:
Class-Path: area.jar servlet.jar images/
在Class-Path头中列出的URL如果不是以“/”结尾,就表示是JAR文件,如果以“/”结尾则表示是目录,在上面的例子中,images/就是目录,其中含有Applet或Application所需的资源。
我们还可以使用多个Class-Path头指定多个扩展的URL。例如:
Class-Path: area.jar
Class-Path: servlet.jar
“下载扩展”还可以是“扩展链”,也就是一个“下载扩展”还可以有一个“Class-Path”头指向另一个“扩展”,第二个“扩展”还可以指向第三个“扩展”,……
三、扩展机制的背后
为什么使用“扩展”就不用指明类路径了呢?是因为扩展机制利用了Java平台(1.2版之后)的新类加载机制。当需要为应用程序加载一个新的类时,运行环境从以下位置并且按以下顺序搜索这个新类:
1. 引导(Bootstrap)类:rt.jar文件中的运行时类以及i18n.jar文件中的国际化类。
2. “安装扩展”:JRE中lib/ext目录下JAR文件中的类。
3. 指明的类路径:系统属性java.class.path所指明的路径下的类以及这些路径下的JAR文件中的类。如果类路径中的JAR文件的manifest文件带有Class-Path属性,那么Class-Path所指定的类也会被搜索。默认情况下,java.class.path属性的值是“.”,即当前路径,我们可以通过设置环境变量“CLASSPATH”或者在命令行中使用“-classpath”或“-cp”选项改变这个属性值。
按照上面的顺序,我们可知只有在rt.jar、i18n.jar以及“安装扩展”中没有找到所需的类时,才会在类路径所指的地方进行搜索。我们采用扩展机制时,由于运行环境自动从“安装扩展”中加载所需的类,因此,就不用再特别指明类路径了。