首先了解与下类加载器的功能:
Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:
BootStrap, ExtClassLoader, AppClassLoader
类加载器也是Java类,因为其他是java类的类加载器本身也要被类加载器加载,显然必须有第一个类加载器不是java类,这正是BootStrap。
Java虚拟机中的所有类装载器采用具有父子关系的树形结构进行组织,在实例化每个类装载器对象时,需要为其指定一个父级类装载器对象或者默认采用系统类装载器为其父级类加载。
☆类加载器的委托机制
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。
还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每个类加载器加载类时,又先委托给其上级类加载器。当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则抛ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild方法,即使有,那有多个儿子,找哪一个呢?
对着类加载器的层次结构图和委托加载原理,解释先前将ClassLoaderTest输出成jre/lib/ext目录下的itcast.jar包中后,运行结果为ExtClassLoader的原因。
使用APi里面的类加载器
/** * */ package cn.hncu.reflect.loader; import org.junit.Test; /** * @author xinxin * */ public class TestLoader { /** * @param args */ //仅仅只是测试 public static void main(String[] args) { Class c=null; try { c = Class.forName("cn.hncu.reflect.loader.TestLoader"); } catch (ClassNotFoundException e) { e.printStackTrace(); } //java 类加载器一般3层 //最底层向上为 BootStrap,ExtClassLoader,AppClassLoader ClassLoader loader=c.getClassLoader(); System.out.println(loader);//sun.misc.Launcher$AppClassLoader@6ed8a125 //我们最先调用的就是 AppClassLoader loader=loader.getParent(); System.out.println(loader);//sun.misc.Launcher$ExtClassLoader@503bbcfd loader=loader.getParent(); System.out.println(loader);//null C语言 BootStrap //loader=loader.getParent();为null,不能调用 了 // System.out.println(loader);//null C语言 BootStrap } //AppClassLoader 一般只能添加项目中的文件 @Test public void Test2() { Object obj=null; try { Class c=Class.forName("d:/ex/UserModel.class"); obj = c.newInstance(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("obj:"+obj);//null AppClassLoader 一般只能添加项目中的文件 } }
做自己的类加载器
虚拟机的核心是通过类加载器来加载.class文件,然后进行相应的解析执行。那么我们可以自己做类加载器,手动加载需要的.class以进行解析执行,从而扩展虚拟机的功能。
API 应用程序需要实现 ClassLoader 的子类,以扩展 Java 虚拟机动态加载类的方式。
网络类加载器子类必须定义方法 findClass 和 loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法 defineClass 来创建类实例。
代码演示:
其实就是利用一个函数 DefineClass函数能够把字节数组变成Class对象,也就是说我们只需要把相应的.class的文件读入到字节数组就可以了
自己做的类,主要把.class文件读入字节数组
package cn.hncu.reflect.loader.MyLoader; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; //defineClass将字节数组转换成Class 对象 public class Loader extends ClassLoader{ public Class findclass(String name){ byte[] b=null; try { b = getbyte( name); } catch (Exception e) { e.printStackTrace(); } return defineClass(null, b, 0 ,b.length);//重要函数defineClass } private byte[] getbyte(String name) throws Exception{ FileInputStream in =new FileInputStream(name); byte[] b =new byte[1024]; ByteArrayOutputStream out=new ByteArrayOutputStream();//内存流 int len=0; while((len=in.read(b))!=-1){ out.write(b, 0, len); } out.close(); b=out.toByteArray();//把内存流的返回 return b; } }
下面就是简单的测试而已,把相应的.class文件放进去,然后解析出,就和 getClassLoader()功能一样了
package cn.hncu.reflect.loader.MyLoader; import org.junit.Test; public class TestLoader { @Test public void getCall(){ Loader lo =new Loader(); Class c=lo.findclass("E:/ex/Loader.class"); //里面的字符串可以是任意的带.class文件的路径 try { Object obj=c.newInstance(); System.out.println(obj+"------------"); System.out.println(obj.getClass().getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }