类加载器(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实例。
扩展:
当在做开发中,出现类转换异常时,除了一般的转换外,还得注意是否一个类被两个类加载器加载,若一个类被两个类加载器加载,这两个字节码在内存中是不相等的,这点非常关键。