本章继续讨论classLoader
关于classLoader有几点需要说明一下的:
1.父级加载器parent并不是 父类 加载器:
此时在AppClassLoder的parent属性值为ExtClassLoader
然而:App/Ext classloader 和URLClassLoader之前的继承关系:
2.当程序开始的时候,AppClassLoader和ExtClassLoader中均没有添加任何Class,所以实际上查找和加载类的工作是由URLClassLoader完成的。当然,系统文件比如:java.lang.String类还是由BootstrapClassLoader完成的。
下面我们自己创建一个ClassLoader。该classloader作用是当传入.java文件时可以直接运行。当读入.java文件时,我们会先判断该文件是否存在且对应的.class文件为最新,否则先进行编译,再进行加载。
工程目录结构如下:
其中 MyClassLoader是个人的类加载器,它继承自ClassLoader;
Hello.java是我们要通过MyClassLoader加载的类,这两个类放在同一个包下。
当然,事物的发展都是从简单到复杂再到简单,在此我们先从最简单的开始。这个比较适合本鸟菜
Hello.java 这个类非常简单,就是把传入的参数都输出出来:
package com.taobao.mm.august; public class Hello { /** * @param args */ public static void main(String[] args) { for(String s :args){ System.out.println("运行参数:"+s); } } }
然后来看一下MyClassLoader.java
package com.taobao.mm.august; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Method; public class MyClassLoader extends ClassLoader { /** * 读入一个文件到byte[]中 * @param filename */ private byte[] getBytes(String filename) throws Exception { File file = new File(filename); long len = file.length(); byte[] raw = new byte[(int) len];// 返回的byte[] FileInputStream fin = new FileInputStream(file); // 将文件中读出内容 存入raw 返回值为读取数据个数 int r = fin.read(raw); if (r != len) { throw new IOException("file can not be read correctly! filename:" + filename); } fin.close(); return raw; } // 定义编译制定Java文件方法 private boolean compile(String javaFile) throws Exception { System.out.println("先吧文件编译一下:filename:" + javaFile); Process p = Runtime.getRuntime().exec("javac " + javaFile); try { p.waitFor();//等待当前线程结束 才允许执行其他线程 } catch (Exception e) { System.out.println(e); } int ret = p.exitValue();//如果返回0 表示正常退出 return ret ==0 ; } //重写ClassLoader的findClass方法 protected Class<?> findClasses(String name) throws ClassNotFoundException{ //先加入以下自己的文件路径 这个需要优化 String classPath = "D:/程序/ForJava/BlogTest/src/"; Class clz = null;//声明一个Class对象 //将包路径中的点.替换成斜线/ String subFile = name.replace(".", "/"); String javaFileName = classPath+subFile+".java"; String classFileName = classPath+subFile+".class"; File javaFile = new File(javaFileName); File classFile = new File(classFileName); //如果文件存在 同时 .class文件不存在或者Java文件进行了修改 if(javaFile.exists()&& (!classFile.exists()|| javaFile.lastModified()>classFile.lastModified())){ try { if(!compile(javaFileName)||!classFile.exists()){ throw new ClassNotFoundException("compile error!"+javaFileName); } } catch (Exception e) { System.out.println(e); } } if(classFile.exists()){ try { byte[] raw = getBytes(classFileName); clz = defineClass(name,raw,0,raw.length);//调用系统自带方法 该方法为final方法,将byte转化为对应内存数据结构 } catch (Exception e) { } } if(clz == null){// throw new ClassNotFoundException("class not found! "+ classFileName); } return clz; } public static void main(String[] args) throws Exception { //同目录下Hello.java good为其参数 String[] arg = {"com.taobao.mm.august.Hello","good"}; //参数检查 if(arg.length<1){ System.out.print("缺少运行时参数,请按照如下格式运行java文件"); System.out.print("java MyClassLoader ClassName"); } //需要加载的类名 String progClass = arg[0]; //保存其余变量 String progArgs[] = new String[arg.length-1]; System.arraycopy(arg, 1,progArgs, 0, progArgs.length); MyClassLoader ycl = new MyClassLoader(); //获取Hello类对应的Class实例 Class<?> clazz = ycl.findClasses(progClass); //调用Hello中main方法 Method main = clazz.getMethod("main", (new String[0]).getClass()); Object argsArray[] = {progArgs}; //调用静态方法 第一个参数为Null main.invoke(null, argsArray); } }
这个类也很简单,最主要的是重载了CLassLoader中findClass(String name)方法,该方法在URLClassLoader中重载过。
CLassLoader最核心的功能就是找到.class文件并将其读入内存。对于java自带的classLoader,主要是loadClass方法,它的好处之一在于增加了缓存功能,我们可以看到,在每层类加载器中,都设置一个private final Vector<Class<?>> classes = new Vector<>();属性,用来存储该加载器加载过哪些类,提高了类加载效率,也避免类被重复加载。
另一个好处就在于父类委托机制,每次查找它的父类。当我们重新定义自己的ClassLoader的时候,最好的方式是重载findClass方法,这样可以继续使用这两点好处,并且又达到了自己的目的。
注:上例源于疯狂java(李刚),本鸟菜觉得拿来学习很合适作为初学者的练习。
以上程序仍存在问题:
1.关于类路径如何指定,试了好几次,发现如果不加上全路径名就无法识别该文件,看来需要重新修改文件加载方式,这样的硬编码不是好习惯,练习还好,实际编码就会很麻烦。可以考虑增加setter方法
2.这个类仅支持.java文件加载,对于jar等没有考虑,都是可以优化的
在此推荐一个开源的事件调度引擎,http://code.google.com/p/ass-hole/ 同时让我们看一下其中自定义classLoader是如何实现的。
关于反射和代理以后会涉及,最好是结合一段实际业务代码,先模仿再深入。