不同的classloader加载的相同的类,会被jvm认为是不同的类

要想实现热加载,几个原则是要记住的:

  1. 每次实例化新的classloader

  2. 动态加载类文件,比如rul或者文件等等

  3. 记载的类使用反射进行方法调用,或者上溯为接口进行调用。


下面看一个例子:

首先定义一个被调用的简单类AppObject:

package com.dataguru.jvm.classloader;

public class AppObject {

	public void sayHello(){
		System.out.println("Hello 1.");
	}
}

然后自定义一个ClassLoader的子类HotClassLoader:

package com.dataguru.jvm.classloader;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.Class;
import java.lang.ClassLoader;
import java.lang.ClassNotFoundException;
import java.lang.Override;
import java.lang.String;
import java.lang.System;


public class HotClassLoader extends ClassLoader {

	public HotClassLoader() {
		// TODO Auto-generated constructor stub
	}
	public HotClassLoader(ClassLoader cl) {
		super(cl);
	}
	
	@Override
	public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {
	 {
		// First, check if the class has already been loaded
		 	Class<?> re = null;
		 	try{
		 		re = findClass(name);
		 	}catch(SecurityException se){
		 		System.out.println(se.getMessage());
		 	}
		    if(re == null){
		        System.out.println("无法载入类:"+name+" 需要请求父加载器");
		        return super.loadClass(name,resolve);
		    }
		    return re;
	    }
	}
	
	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		
		Class<?> cl = null;
		try {
			byte[] data = loadClassBytes(name);
			cl = super.defineClass(name, data, 0, data.length); 
		} catch (IOException e) {
			e.printStackTrace();
		}
		return cl;
	}
	
	public static byte[] loadClassBytes(String clazzName) throws IOException{
		String resourceName = "/".concat(clazzName.replaceAll("[.]", "/")).concat(".class");
		System.out.println("resource Location:"+resourceName);
		InputStream is = TestMain.class.getResourceAsStream(resourceName);
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		byte[] bt = new byte[1024];
		int l = 0;
		while((l = is.read(bt)) > 0 ){
			baos.write(bt,0,l);
		}
		byte[] rst= baos.toByteArray();
		is.close();
		baos.close();
		return rst;
	}
	
}

最后写测试类进行测试:

/**
 * 
 */
package com.dataguru.jvm.classloader;

import java.lang.reflect.Method;

/**
 * @author ShenLi
 *
 */
public class TestHotLoad {

	/**
	 * @param args
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		HotClassLoader mcl = null;
		
		
		while(true){
			mcl = new HotClassLoader();
			Class<?> clz =  mcl.loadClass("com.dataguru.jvm.classloader.AppObject", true);
			mcl = new HotClassLoader();
			Object o = clz.newInstance();
			System.out.println(o);
			Method m = o.getClass().getMethod("sayHello", new Class[] {});
			m.invoke(o, new Object[] {});
			
			Thread.sleep(5000);
		}
	}

}

注意,每次生成新的classLoader实例,是因为通一个classLoader不能两次加载相同的类,否则会报错。生成的类实例,也不能直接new AppObject()而是要通过反射来调用,或者上溯为接口(还未测试)


测试输出:

resource Location:/com/dataguru/jvm/classloader/AppObject.class
resource Location:/java/lang/Object.class
Prohibited package name: java.lang
无法载入类:java.lang.Object 需要请求父加载器
com.dataguru.jvm.classloader.AppObject@33909752
resource Location:/java/lang/System.class
Prohibited package name: java.lang
无法载入类:java.lang.System 需要请求父加载器
resource Location:/java/io/PrintStream.class
Prohibited package name: java.io
无法载入类:java.io.PrintStream 需要请求父加载器
Hello 1.



将程序改为hello 2后保存编译

这时测试程序并没有退出,而是继续输出,但是输出改变了:

resource Location:/com/dataguru/jvm/classloader/AppObject.class
resource Location:/java/lang/Object.class
Prohibited package name: java.lang
无法载入类:java.lang.Object 需要请求父加载器
com.dataguru.jvm.classloader.AppObject@7f31245a
resource Location:/java/lang/System.class
Prohibited package name: java.lang
无法载入类:java.lang.System 需要请求父加载器
resource Location:/java/io/PrintStream.class
Prohibited package name: java.io
无法载入类:java.io.PrintStream 需要请求父加载器
Hello 2.


之后反复修改类,均可以实现动态加载。