Java 程序包含很多类文件,每一个都与单个Java类相对应,这些类文件不像静态C程序,一次性加载入内存,它们随时需要随时加载。这就是类加载器与众不同的地 方。它从源文件(通常是.class 或 .jar文件)获得不依赖平台的字节码,然后将它们加载到JVM内存空间,所以它们能被解释和执行。默认状态下,应用程序的每个类由 java.lang.ClassLoader加载。因为它可以被继承,所以可以自由地加强其功能。
使用自定义类加载器的原因
默认的 java.lang.ClassLoader仅仅可以从加载本地文件系统的类。Java被设计成不论本地磁盘或网络都有足够的弹性加载类,并且可以在加载 之前处理特殊事物。例如:应用程序可以检查Web站点或FTP上插入类的更新版本并且自动校验数字签名确保执行可信任的代码。许多众所周知的软件都使用自 己的类加载器。
通常默认加载器是所谓的bootstrap类加载器;它负责加载诸如java.lang.Object等关键类和加 载其他rt.jar文件的运行时代码到内存。因为Java语言规范没有提供bootstrap类加载器的详细信息,不同的JVM可能有不同的类加载器。如 果看到网页上有applets在运行,则它使用的是自定义类加载器。嵌入到浏览器中的applet阅读器包含了可以访问远程服务器上站点的类加载器,它可 以通过HTTP加载原始字节码文件,并且在JVM中将它们转换成类。
类加载器(除了bootstrap类加载器)有父类加载器,这些父类是基本加载器的加载器实例。最重要的一点是设置正确的父加载器。然后可以使用 类加载器的getParent()方法实现委派类请求(例如:自定义类加载器找不到使用专门方法的类时)。此时必须为将父加载器作为 java.lang.ClassLoader构造器的参数:public class MyClassLoader extends ClassLoader
{
public MyClassLoader()
{
super(MyClassLoader.class.getClassLoader());
}
}
loadClass(String name)方法是ClassLoader的入口。名字参数是完全资格类名(FQCN),例如关于包类名。如果父加载器设置正确,当请求 MyClassLoader中的loadClass(String name)方法加载类,但又找不到需要加载的类时,则首先会询问父加载器。如果父加载器也找不到此类,则调用findClass(String name)方法。默认状态下findClass(String name)会抛出ClassNotFoundException例外,很多开发人员都很清楚这个例外。自定义类加载器的开发者都希望从 java.lang.ClassLoader继承时跳过这个方法。
findClass()方法的目标是为MyClassLoader容纳所有专门代码,此时不需要重复其他代码(例如当加载失败时调用系统 ClassLoader)。在此方法中,ClassLoader需要从原文件中获取字节码。一旦找到字节码则会调用defineClass()方法。 ClassLoader实例调用此方法是非常重要的。因此,如果两个ClassLoader实例定义了来自不同或相同原文件的字节码,则被定义的类也将区 别对待。
我们给出两个相似的类加载器MyClassLoader1 和 MyClassLoader2,它们都可以从相同的源文件找到MyCoolClass字节码。如果一个程序通过这两个加载器分别独立加载 MyCoolClass实例(coolClass1通过MyClassLoader1加载, coolClass2通过MyClassLoader2加载),MyCoolClass.class能够被独立定义。执行下面的代码:
MyCoolClass coolClass1 = (MyCoolClass)coolClass2;
将得到一个ClassCastException例外。(开发者如果没有很好的理解类加载机制则经常碰到这样的情况。)因为它们是不同的加载器 所定义的,JVM将它们看成不同的类。虽然它们是相同类型的类并且从相同的源文件加载,但是变量coolClass1和coolClass2不兼容。
不论是否跳过findClass() 或 loadClass(),getSystemClassLoader()方法将以实际ClassLoader对象的形式直接访问系统 ClassLoader。也可以通过调用findSystemClass(String name)方法间接访问。getParent()方法允许获得父加载器。 Listing A 给出了可以运行的自定义类加载器示例。