类加载器(class loader)用来加载 Java 字节码到 java虚拟机中,即类加载器负责读取 Java 字节代码,并转换成 java.lang.Class
类的一个实例。每个这样的实例用来表示一个 Java 类。
在java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器:
BookStrap, ExtClassLoader, AppClassLoader
类加载器也是java类,所以java类加载器本身也要被其它类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap.
java虚拟机中所有的类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定父级类加载器
类加载器之间的父子关系和管辖范围
可以通过下面的测试程序验证类加载器的委托机制:
public class ClassLoderTest1 {
public static void main(String[] args) {
ClassLoader loder = ClassLoderTest1.class.getClassLoader();
while (loder != null) {
System.out.println(loder.getClass().getName());
loder = loder.getParent();
}
}
}
/*
* sun.misc.Launcher$AppClassLoader
* sun.misc.Launcher$ExtClassLoader
*/
从结果可以看出,首先AppClassLoader会加载ClassLoderTest1.class,由于委派机制会先使用其父加载器ExtClassLoader去加载,这就是为什么打印出上面的结果。
下面介绍下ClassLoader类
java.lang.ClassLoader
类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class
类的一个实例。除此之外,ClassLoader
还负责加载 Java 应用所需的资源,如图像文件和配置文件等
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 类。 |
当我们自定义类加载器时会用到上面的方法。
下面我们来自定义加载器实现加载特定目录下面的.class文件。
这里我们来加载lib目录下面的字节码文件。
首先我们来定义一个目标文件
public class ClassLoaderDemo extends Date {
public String toString() {
return "这是被类加载器加载的哦";
}
}
让其实现Date类是便于后面的测试
然后我们开始编写自定义的类加载器了
public class CustomClassLoader extends ClassLoader {
/**
* 复写findClass()方法
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = getClassData(name);
if (data == null) {
throw new ClassNotFoundException();
}
//通过byte[]数组得到Class
return defineClass(null, data, 0, data.length);
}
/**
* 从lib目录中得到字节码文件,并转成byte[]数组
* @param name
* @return
*/
private byte[] getClassData(String name) {
String classSrc = "lib" + File.separator + getClassName(name) + ".class";
try {
FileInputStream in = new FileInputStream(classSrc);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024*4];
int len = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
return out.toByteArray();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
*
* @param classNamePath 形如:cn/zcl/lib/ClassLoaderDemo.class
* @return ClassLoaderDemo.class
*/
private String getClassName(String classNamePath) {
return classNamePath.substring(classNamePath.lastIndexOf(".") + 1);
}
}
其实实现类加载器只需继承ClassLoader,并覆写里面的findClass()方法。
最后测试一个测试类来测试我们写的类加载器是否成功
public class ClassLoaderTest2 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Date date = (Date)new CustomClassLoader().loadClass("cn.zcl.classLoaderDemo").newInstance();
System.out.println(date);
}
}
打印结果:
这是被类加载器加载的哦
这就表明我们写得自定义加载器已经成功。
下面总结下:
编写自定义的类加载器只需继承ClassLoader,并覆写里面的findClass()方法,将字节码文件转成Class实例,当然这里面使用到了defineClass()将字节数组转成Class实例。
扩展:
当在做开发中,出现类转换异常时,除了一般的转换外,还得注意是否一个类被两个类加载器加载,若一个类被两个类加载器加载,这两个字节码在内存中是不相等的,这点非常关键。