本章继续讨论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是如何实现的。
关于反射和代理以后会涉及,最好是结合一段实际业务代码,先模仿再深入。