Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap, ExtClassLoader, AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
☆类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的aa.jar包中后,运行结果为ExtClassLoader的原因。
做自己的类加载器
虚拟机的核心是通过类加载器来加载.class文件,然后进行相应的解析执行。那么我们可以自己做类加载器,手动加载需要的.class以进行解析执行,从而扩展虚拟机的功能。
网络类加载器子类必须定义方法findClass 和loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法defineClass 来创建类实例。
package cn.hncu.classLoader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import org.junit.Test;
import cn.hncu.reflect.Person;
/*注意:::路径名一点要和clathPath下一致
类加载器加载文件时是以classpath为根目录的,然后按照你给出的路径去加载文件。*/
/*
* 自制类加载器的基本思路:
* 1、把字节码文件的数据读取(或存放)到一个字节数据buf[]当中
* 2、利用ClassLoader类中的defineClass()方法把buf[]中的数据生成一个Class对象返回出来。
*/
public class MyClassLoader extends ClassLoader{
//使用指定的二进制名称查找类。此方法应该被类加载器
//的实现重写,该实现按照委托模型来加载类。在通过父类加载器检查所请求的类后
//,此方法将被 loadClass 方法调用。默认实现抛出一个 ClassNotFoundException。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//String pathName="G:\\aaa\\cn\\hncu\\Person.class";//这里是路径名字
byte[] buf=null;
try {
//buf = loadData(name);
ArrayList<Byte> list= loadData2(name);
Object objs[]=list.toArray();
buf=new byte[objs.length];
for(int i=0;i<objs.length;i++){
buf[i]=(Byte) objs[i];
}
} catch (IOException e) {
e.printStackTrace();
}
Class<?> c=defineClass("cn.hncu.MyJUinit.A", buf, 0, buf.length);//这里是类名字
return c;
}
//通过IO或网络把字节码数据读取到buf[]当中。进一步地,如果我们自己熟悉字节码的生成格式,那么也可自己用程序生成。
//本例,我们是把硬盘中的一个外部字节码文件的数据读取到buf[]当中
private byte[] loadData(String name) throws IOException {
byte buf[]=new byte[1024];
byte[] b;
FileInputStream fin=new FileInputStream(name);
ByteArrayOutputStream bout=new ByteArrayOutputStream();
int len=0;
while ((len = fin.read(buf)) != -1) {
bout.write(buf, 0, len);
}
bout.close();
b=bout.toByteArray();
return b;
}
@Test
public void testMyClassLoader() throws ReflectiveOperationException{
Object obj=Class.forName("cn.hncu.reflect.Person").newInstance();
Person p=(Person) obj;
System.out.println(p);
Object obj2=findClass("G:\\aaa\\cn\\hncu\\reflect\\Person.class").newInstance();//注意路径名一点要和clathPath下一致
System.out.println(obj);
//※不同类加载器加载的对象是无法强转---可以理解是不同的生存空间
Person p2=(Person)obj2;
System.out.println(p2);
}
private ArrayList<Byte> loadData2(String name) throws IOException {
ArrayList<Byte> list=new ArrayList<Byte>();
byte[] b;
FileInputStream fin=new FileInputStream(name);
int len=0;
while ((len = fin.read()) != -1) {
System.out.println("c");
list.add((byte) len);
}
return list;
}
public Class<?> myFindClass(String name) throws ReflectiveOperationException{
return findClass(name);
}
}