鸟菜基础篇_8_19(classLoader)下

本章继续讨论classLoader

关于classLoader有几点需要说明一下的:

1.父级加载器parent并不是 父类 加载器:

此时在AppClassLoder的parent属性值为ExtClassLoader 

鸟菜基础篇_8_19(classLoader)下_第1张图片


然而:App/Ext classloader 和URLClassLoader之前的继承关系:

鸟菜基础篇_8_19(classLoader)下_第2张图片


2.当程序开始的时候,AppClassLoader和ExtClassLoader中均没有添加任何Class,所以实际上查找和加载类的工作是由URLClassLoader完成的。当然,系统文件比如:java.lang.String类还是由BootstrapClassLoader完成的。

下面我们自己创建一个ClassLoader。该classloader作用是当传入.java文件时可以直接运行。当读入.java文件时,我们会先判断该文件是否存在且对应的.class文件为最新,否则先进行编译,再进行加载。

工程目录结构如下:

鸟菜基础篇_8_19(classLoader)下_第3张图片

其中 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是如何实现的。

关于反射和代理以后会涉及,最好是结合一段实际业务代码,先模仿再深入。









你可能感兴趣的:(鸟菜基础篇_8_19(classLoader)下)