JVM 中除根加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader的子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。
ClassLoader中包含了大量的proteted方法--- 这些方法都可被子类重写, ClassLoader有如下三个关键方法:
- loadClass(String name,boolean resolve ) :该方法为ClassLoader的入口点,根据指定的二进制名称加载类,系统就是调用ClassLoader的方法来获取指定类对应的Class对象。
- findClass(String name) :根据二进制名称来查找类。
如果需要实现自定义的ClassLoader,可以通过重写以上两个方法来实现,当然我们推荐重写findClass()方法,而不是重写loadClass()方法。因为loadClass()方法的执行步骤如下:
- 用findLoadedClass(String ) 来检查是否已经加载类,如果已经加载则直接返回。
- 在父类加载器上调用loadClass方法。如果父类加载器为null,则使用根类加载器来加载。
- 调用findClass(String ) 方法查找类。
从上面步骤中可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托、缓冲机制两种策略。如果重写loadClass()方法,则实现逻辑更为复杂。
在ClassLoader里还有一个核心方法:Class defineClass
(String name, byte[] b,int off,int len ) .该方法负责将指定类的字节码文件转化为class对象,该字节码文件可以来源于文件网络.
下面是自定义的类加载器:
package com.reflect; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class CompileClassLoader extends ClassLoader { String path="D:/mavenwork/mianshi/src/"; //读取一个文件的内容 private byte[] getBytes(String fileName) throws IOException{ File file=new File(fileName); long len=file.length(); byte[] raw=new byte[(int) len]; FileInputStream fin=new FileInputStream(file); //一次读取class文件的全部二进制数据 int r=fin.read(raw); if(r!=len){ throw new IOException("无法读取全部文件:"+r+"!="+len); } fin.close(); return raw; } //定义编译指定JAVA文件的方法 private boolean compile(String javaFile) throws IOException{ System.out.println("CompileClassLoader:正在编译"+javaFile+"..."); //调用系统的javac命令 Process p=Runtime.getRuntime().exec("javac "+javaFile); try { //其他线程都等待这个线程完成 p.waitFor(); } catch (Exception e) { System.out.println(e); } //获取javac线程的退出值 int ret=p.exitValue();//此 Process 对象表示的子进程的出口值。根据惯例,值 0 表示正常终止。 //返回编译是否成功 return ret==0; } //重写ClassLoader的findClass方法 protected Class<?> findClass(String name) { Class clazz=null; //将包路径中的点(.)替换成斜线(/) String fileSub=path+name.replace(".", "/"); String javaFilename=fileSub+".java"; String classFilename=fileSub+".class"; File javaFile=new File(javaFilename); File classFile=new File(classFilename); //当指定的Java源文件存在,且class文件不存在;或Java 源文件的修改时间比class文件的修改时间晚时,重新编译 if(javaFile.exists()&&(!classFile.exists()||javaFile.lastModified()>classFile.lastModified())){ //如果编译失败,或者该class文件不存在 try { if (!compile(javaFilename) || !classFile.exists()) { throw new ClassNotFoundException("ClassNotFoundException:"+javaFilename); } } catch (Exception e) { e.printStackTrace(); } } //如果class文件存在,系统负责将该文件转换成Class对象 if(classFile.exists()){ try { //将class文件的二进制数据读入数组 byte[] raw=getBytes(classFilename); //调用classLoader的defineClass方法将二进制数据转换成Class对象 clazz=defineClass(name, raw, 0,raw.length); } catch (Exception e) { e.printStackTrace(); } } //如果clazz为null,表明加载失败,则抛出异常 if(clazz==null){ try { throw new ClassNotFoundException(name); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return clazz; } //定义一个主方法 public static void main(String[] args) throws Exception { //如果运行该程序时没有参数,即没有目标类 if(args.length<1){ System.out.println("缺少运行的目标类,请按如下格式运行Java源文件:"); System.out.println("类的全路径名 参数,如: com.reflect.Hello 参数1"); } //第一个参数是需要运行的类 String progClass=args[0]; //剩下的参数作为运行目标类时的参数,所以将这些参数复制到一个新数组中 String progArgs[]=new String[args.length-1]; System.arraycopy(args, 1, progArgs, 0, progArgs.length); CompileClassLoader ccl=new CompileClassLoader(); //加载需要运行的类 Class<?> clazz=ccl.loadClass(progClass); //获取需要运行类的主方法 Method main=clazz.getMethod("main", (new String[0]).getClass()); Object argsArray[]={progArgs}; main.invoke(null, argsArray); } }
随意提供一个简单的主类,如Hello.java (包含main方法.)
package com.reflect; public class Hello { public static void main(String[] args) { for(String arg:args){ System.out.println("运行Hello的参数:"+arg); } } }