Java是一种极具动态性的语言。类似Windows的动态链接库,Java应用程序总是被编译成若干个单独的class文件,
程序执行时根据需要由java虚拟机动态载入相关的类。这种机制使编写动态的分布式应用程序成为可能:我们可以
在客户端编写自己的类载入器,而真正执行的程序却存放在本地、局域网或世界另一端的主机上。下面将介绍
如何在应用程序中实现java的动态类载入机制。
与动态类载入有关的系统类为支持动态类载入机制,系统类组java.lang中提供了两个类:Class类和ClassLoader
类。
Class类
在java虚拟机中,每一个类或接口都是由Class类来操纵的,它不能被显式的的实例化。必须使用其他方
法来获取Class类的对象。动态类载入机制的关键在于如何获得指定类的Class对象。相关方法主要有:
public static Class forName(String className)
这是一个静态方法,它获取指定名字的类的Class类型对象,类名可以是像"sun.applet.Applet"这样的字符串,但
不能带有路径或网络地址等信息。这是从本地系统中动态载入类的最方便的办法。
public Object newInstance()
这是最重要的一个方法,它建立由Class类型对象描述的指定类的实例。
ClassLoader类
这是一个抽象类,如果打算运用它,必须继承它并重写它的loadClass()方法。其主要方法有:
protected ClassLoader()
这是一个构建元,可以用它建议一个ClassLoader类的实例。注意:继承这个类的类必须重写这个方法,而不能使
用缺省的构建元。
protected abstract Class loadClass(String name, boolean resolve)
载入指定的类数据,建立Class类型的对象并根据需要解析它。这是一个抽象方法。大家必须在自己的子类中
重写这个方法,重写的规则可以参考第三部分的例子。
protected final Class defineClass(byte data[], int offset, int length)
将字节数组中的数据定义为Class类型的对象,字节数组的格式由虚拟机规定。
protected final Class findSystemClass(String name)
根据指定的类名载入类,它会自动在当前目录和环境变量classpath指定的路径中寻找,如果找不到,则会抛出
ClassNotFouondException异常。
protected final void resolveClass(Class c)
通过载入与指定的类相关的所有类来解析这个类,这必须在类使用之前完成。
扩充ClassLoader类以实现动态类载入
理解动态类载入机制最好的办法是通过例子,下面这个完整的例子由四个类组成,分别解释如下。
1.MyClassLoader类是ClassLoader类的子类,它重写了loadClass方法,实现了将网络上用URL地址指定的类动态载
2.由于Java是强类型检查语言,通过网络载入的类被实例化后只是一个Object类型的对象,虚拟机并不知道它包含
哪些方法,应从哪个方法开始执行。因此,可以被动态载入的类必须继承某一个抽象类或实现某一个接口,因为父
类只能有一个,所以通常用实现特定接口的办法。下面的代码定义了一个接口类Share和它的方法start().
3.TestClassLoader类通过使用MyClassLoader类的loadClass()方法,将指定URL地址的类载入并在本地系统执行它
,从而实现了类的动态载入(在执行被载入类的方法前一定要将它进行强制类型转换)
4.Tested类很简单,可以将它将在任何WEB服务器圣桑,但应注意能动态载入且被执行的类,一定要实现预先定义
的接口中的方法,下面的例子实现了接口Share中的start方法。
1.开发分布式应用。这对开发远程的客户端应用程序最有用,客户端仅需要安装一些基本的系统和一个能实现动态
类载入机制的类;需要本地系统不存在的功能时,仅需要从网络动态载入并执行相应类即可获得特定功能。因为客
户端使用的总是软件的最新版本,所以不再有软件的升级和维护问题,即实现了所谓的“零管理”模式。
2.对.class文件加密。由于Java的字节码容易被反编译,大部分开发java应用程序的公司均担心自己的成果被别人
不劳而获。其实可以将类文件进行适当的加密处理,执行时使用自己的类载入器进行相应的解密,就可以解决这个
问题。
3.使第三方开发者易于扩展应用。从前面可知,所有可以被类载入器动态载入并执行的类,必须继承定义的类或实
现定义的接口,这样,可以制定一些规则,使其他开发者不必了解应用程序也可以扩充功能。
当然,有利必有弊,在网络中使用动态类载入的主要缺陷在于安全性,很可能会载入恶意代码,这个问题要靠java
的安全管理器和适当的加密算法来解决。
程序执行时根据需要由java虚拟机动态载入相关的类。这种机制使编写动态的分布式应用程序成为可能:我们可以
在客户端编写自己的类载入器,而真正执行的程序却存放在本地、局域网或世界另一端的主机上。下面将介绍
如何在应用程序中实现java的动态类载入机制。
与动态类载入有关的系统类为支持动态类载入机制,系统类组java.lang中提供了两个类:Class类和ClassLoader
类。
Class类
在java虚拟机中,每一个类或接口都是由Class类来操纵的,它不能被显式的的实例化。必须使用其他方
法来获取Class类的对象。动态类载入机制的关键在于如何获得指定类的Class对象。相关方法主要有:
public static Class forName(String className)
这是一个静态方法,它获取指定名字的类的Class类型对象,类名可以是像"sun.applet.Applet"这样的字符串,但
不能带有路径或网络地址等信息。这是从本地系统中动态载入类的最方便的办法。
public Object newInstance()
这是最重要的一个方法,它建立由Class类型对象描述的指定类的实例。
ClassLoader类
这是一个抽象类,如果打算运用它,必须继承它并重写它的loadClass()方法。其主要方法有:
protected ClassLoader()
这是一个构建元,可以用它建议一个ClassLoader类的实例。注意:继承这个类的类必须重写这个方法,而不能使
用缺省的构建元。
protected abstract Class loadClass(String name, boolean resolve)
载入指定的类数据,建立Class类型的对象并根据需要解析它。这是一个抽象方法。大家必须在自己的子类中
重写这个方法,重写的规则可以参考第三部分的例子。
protected final Class defineClass(byte data[], int offset, int length)
将字节数组中的数据定义为Class类型的对象,字节数组的格式由虚拟机规定。
protected final Class findSystemClass(String name)
根据指定的类名载入类,它会自动在当前目录和环境变量classpath指定的路径中寻找,如果找不到,则会抛出
ClassNotFouondException异常。
protected final void resolveClass(Class c)
通过载入与指定的类相关的所有类来解析这个类,这必须在类使用之前完成。
扩充ClassLoader类以实现动态类载入
理解动态类载入机制最好的办法是通过例子,下面这个完整的例子由四个类组成,分别解释如下。
1.MyClassLoader类是ClassLoader类的子类,它重写了loadClass方法,实现了将网络上用URL地址指定的类动态载
入,取得它的Class类型对象。读者可根据自己载入类的具体方式改写下面代码。
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.Hashtable;
@SuppressWarnings("all")
public class MyClassLoader extends ClassLoader {
private Hashtable loadedClasses;
// 必须定义
public MyClassLoader() {
loadedClasses = new Hashtable();
}
public synchronized Class<?> loadClass(String className, boolean resolve) throws
ClassNotFoundException {
Class newClass;
byte[] classData; // 存放类的字节数组
// 检查要载入的类数据是否已经被保存在哈希表中
newClass = (Class) loadedClasses.get(className);
// 如果类数据已经存在且resolve值为true,则解析它
if (newClass != null) {
if (resolve) resolveClass(newClass);// 链接类
return newClass;
}
/*
* 首先必须从本地类组中载入指定类,因为虚拟机将这个类载入后,在
* 解析和执行它时所用到的任何其他类等,均不再使用虚拟机的类载入器
* 而是调用我们自制的类载入器来加载
*/
try {
newClass = findSystemClass(className);
return newClass;
} catch (ClassNotFoundException e) {
System.out.println(className + " is not a system class");
}
// 如果不是系统类,则试图从网络中指定的URL地址载入类
try {
// 用自定义方法载入数据,存放于字节数组classData中
classData = getClassData(className);
// 用字节数组包含的数据及案例一个class类的对象
newClass = defineClass(classData, 0, classData.length);
if (newClass == null) throw new ClassNotFoundException(className);
} catch (Exception e) {
throw new ClassNotFoundException(className);
}
// 如果类正确载入,则将类保存在哈希表中
loadedClasses.put(className, newClass);
if (resolve) {
resolveClass(newClass);
}
return newClass;
}
protected byte[] getClassData(String className) throws IOException {
byte[] data;
int length;
try {
// 从网络中采用URL类的方式载入指定URL地址的类数据
URL url = new URL(className.endsWith(".class") ? className : className +
".class");
URLConnection conn = url.openConnection();
InputStream is = conn.getInputStream();
length = conn.getContentLength();// 返回 content-length 头字段的值
data = new byte[length];
is.read(data);
is.close();
return data;
} catch (Exception e) {
throw new IOException(className);
}
}
}
2.由于Java是强类型检查语言,通过网络载入的类被实例化后只是一个Object类型的对象,虚拟机并不知道它包含
哪些方法,应从哪个方法开始执行。因此,可以被动态载入的类必须继承某一个抽象类或实现某一个接口,因为父
类只能有一个,所以通常用实现特定接口的办法。下面的代码定义了一个接口类Share和它的方法start().
public interface Share {
public void start(String[] args);
}
3.TestClassLoader类通过使用MyClassLoader类的loadClass()方法,将指定URL地址的类载入并在本地系统执行它
,从而实现了类的动态载入(在执行被载入类的方法前一定要将它进行强制类型转换)
public class TestClassLoader {
@SuppressWarnings("all")
public static void main(String[] args) {
MyClassLoader loader = new MyClassLoader();
Class cc;
Object oo;
String ss = "http://localhost:8888/Tested.class";
if (args.length != 0) ss = args[0];
try {
System.out.println("Loading class " + ss + " ...");
// 使用重写的方法loadClass载入类数据
cc = loader.loadClass(ss);
System.out.println("Create instance...");
oo = cc.newInstance();
System.out.println("Call start method");
// 强制类型转换后执行载入类中的方法
((Share) oo).start(args);
} catch (Exception e) {
System.out.println("Caught exception: " + e);
}
}
}
4.Tested类很简单,可以将它将在任何WEB服务器圣桑,但应注意能动态载入且被执行的类,一定要实现预先定义
的接口中的方法,下面的例子实现了接口Share中的start方法。
public class Tested implements Share {
@Override
public void start(String[] args) {
System.out.println("动态载入类成功");
for (String arg : args) {
System.out.println(arg);
}
}
}
将Tested放到tomcat根目录下运行:
1.开发分布式应用。这对开发远程的客户端应用程序最有用,客户端仅需要安装一些基本的系统和一个能实现动态
类载入机制的类;需要本地系统不存在的功能时,仅需要从网络动态载入并执行相应类即可获得特定功能。因为客
户端使用的总是软件的最新版本,所以不再有软件的升级和维护问题,即实现了所谓的“零管理”模式。
2.对.class文件加密。由于Java的字节码容易被反编译,大部分开发java应用程序的公司均担心自己的成果被别人
不劳而获。其实可以将类文件进行适当的加密处理,执行时使用自己的类载入器进行相应的解密,就可以解决这个
问题。
3.使第三方开发者易于扩展应用。从前面可知,所有可以被类载入器动态载入并执行的类,必须继承定义的类或实
现定义的接口,这样,可以制定一些规则,使其他开发者不必了解应用程序也可以扩充功能。
当然,有利必有弊,在网络中使用动态类载入的主要缺陷在于安全性,很可能会载入恶意代码,这个问题要靠java
的安全管理器和适当的加密算法来解决。