ClassLoader 中与加载类相关的方法
方法 |
说明 |
getParent() |
返回该类加载器的父类加载器 |
loadClass(String name) |
加载名称为name的类,返回的结果是java.lang.Class类的实例 |
findClass(String name) |
查找名称为name的类,返回的结果是java.lang.Class类的实例 |
findLoadedClass(String name) |
查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例 |
defineClass(String name, byte[] b, int off, int len) |
把字节数组b中的内容转换成 Java 类,返回的结果是java.lang.Class类的实例。这个方法被声明为final的 |
resolveClass(Class<?> c) |
链接指定的 Java 类 |
系统提供的类加载器
•引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。
•扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
•系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader()来获取它。
三个加载器工作的目录
•这三个加载器就构成我们的Java类加载体系。他们分别从以下的路径寻找程序所需要的类:
•BootstrapLoader:sun.boot.class.path
•ExtClassLoader:java.ext.dirs
•AppClassLoader:java.class.path
•这三个系统参量可以通过System.getProperty()函数得到具体对应的路径。
public class ClassLoaderPathTest {
public static void main(String args[]) {
try{
System.out.println("BootstrapLoader's Load path sun.boot.class.path="+
System.getProperty("sun.boot.class.path"));
System.out.println("ExtClassLoader's Load path java.ext.dirs="+
System.getProperty("java.ext.dirs"));
System.out.println("BootstrapLoader's Load path java.class.path="+
System.getProperty("java.class.path"));
}catch(Exception e){
e.printStackTrace();
}
}
}
类加载器的树状组织结构
•有些 JDK 的实现对于父类加载器是引导类加载器的情况,getParent()方法返回null
类加载器的代理模式
•类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。
•Java虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。
•代理模式是为了保证 Java 核心库的类型安全。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
加载类的过程
•真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用defineClass来实现的;而启动类的加载过程是通过调用loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类com.example.Outer引用了类com.example.Inner,则由类com.example.Outer的定义加载器负责启动类com.example.Inner的加载过程。

开发自己的类加载器
•一般来说,自己开发的类加载器只需要覆写findClass(String name)方法即可。java.lang.ClassLoader类的方法loadClass()封装了前面提到的代理模式的实现。该方法会首先调用findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写loadClass()方法,而是覆写findClass()方法。
自定义类加载器代码示例:
package classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}