类加载器
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap, ExtClassLoader, AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
☆ 类加载器之间的父子关系和管辖范围
爷爷类:BootStrap--->JRE/lib/rt.jar
父亲类:ExtClassLoader--->JRE/lib/ext/*.jar
SystemclassLoader--->AppClassLoader--->classpath指定的所有jar或目录
☆类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
下面来演示一下,使用ExtClassLoader加载器加载类
首先要把我们需要加载的类,编译生成的.class文件打包成.jar包(这里主要一定要从包的根目录开始打包),在把jar包放你运行环境的JRE/lib/ext/目录下。
Person
package cn.hncu.classLoader;
public class Person {
private String name;
public Person() {
}
@Override
public String toString() {
System.out.println("AppClassLoader");
return "Person [name=" + name + "]";
}
}
测试:
@Test
public void demo() throws Exception{
Class c=Class.forName("cn.hncu.classLoader.Person");
Object obj=c.newInstance();
System.out.println(c.getClassLoader());
System.out.println(obj.toString());
}
结果:
sun.misc.Launcher$ExtClassLoader@145a25f3
Person [name=null]
做自己的类加载器
应用程序需要实现 ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。
网络类加载器子类必须定义方法 findClass 和 loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法 defineClass 来创建类实例。
下面我们来做一个自己的类加载器:
package cn.hncu.classLoader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import org.junit.Test;
/**
* @author<a href="mailto:794530831@qq.com">dragon_Dai</a>
* @version 2017-8-20 上午12:12:42
* @filename MyClassLoader.java
*
* --自定义加载器
* 主要是实现ClassLoader类的defineClass()方法
* 因为defineClass()方法权限为protected,所以自定义类加载器一般都有extends ClassLoader
*/
public class MyClassLoader extends ClassLoader{
public Class loader(String fileName) throws Exception{
File file=new File(fileName);
if(!file.exists()){
throw new RuntimeException("字节码文件不存在");
}
//因为不知道字节码文件的大小,考虑到字节码文件可能会比较大,这里采用内存流
FileInputStream fis=new FileInputStream(file);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
byte b[]=new byte[512];
int len=-1;
while((len=fis.read(b))!=-1){
baos.write(b, 0, len);
}
baos.close();
byte bs[]=baos.toByteArray();
Class c=defineClass(null, bs, 0, bs.length);
return c;
}
}
测试:
@Test
public void test() throws Exception {
MyClassLoader myClassLoader=new MyClassLoader();
Class c=myClassLoader.loader("E:/a/Person.class");
Object obj=c.newInstance();
System.out.println(c.getClassLoader());
System.out.println(obj);
}
结果:
cn.hncu.classLoader.MyClassLoader@1e73d10f
Person [name=null]