动态加载java类

目标:

当class重新编译后无需重启JVM就能加载更新过的类

术语:

目标类:指需要动态更新的类

对于目标类的限制:

  • 构造函数不能有参数
  • 必须实现一个接口
  • 只对实例方法有效(因为接口中不能有静态方法)
  • 没有考虑全局变量(可以在重新加载时复制原对象的成员,不过目前没实现)

测试代码:

ClassManager manager = new ClassManager();
String className = "com.hrtc.test.Test";//可换成符合上述约束的类
ITest t = (ITest) manager.getInstanceProxy(className);//当然接口也要随之变化
t.test();//test只是输出一段信息

//等待size秒,在这段时间内你必须重新编译生成你的.class文件,这里是com.hrtc.test.Test.class
int size = 5;
int i = 0;
while (i < size) {
	System.out.println(i);
	i++;
	Thread.currentThread().sleep(1000);
}

t.test();//如果你修改了test输出内容则输出的内容会发生变化,此过程中没有重启jvm,也没有重新创建Test(上述代码中)

//测试动态代理和直接访问的效率,发现代理慢得多
long beginTime1 = System.currentTimeMillis();
int count = 10000;
for(int k = 0;k < count;k++){
	t.test();
}
long endTime1 = System.currentTimeMillis();


ITest t2 = new Test();
long beginTime2 = System.currentTimeMillis();
for(int k = 0;k < count;k++){
	t2.test();
}
long endTime2 = System.currentTimeMillis();

System.out.println("proxy time======="+(endTime1-beginTime1));
System.out.println("no proxy time======="+(endTime2-beginTime2));

要解决的问题及解决方法

如何重新加载类

 

  重新加载class示例代码

/**
 * 加载某个类
 * @param c
 * @return
 * @throws IOException
 */
@SuppressWarnings( { "unchecked" })
public Class loadClass(Class c) throws IOException {
	byte[] bs = loadByteCode(c);
	Class cNew = super.defineClass(c.getCanonicalName(), bs, 0, bs.length);
	return cNew;
}

/**
 * 加载某个类的字节码
 * @param c
 * @return
 * @throws IOException
 */
private byte[] loadByteCode(Class c) throws IOException {
	int iRead = 0;
	String path = c.getResource(c.getSimpleName() + ".class").getPath();

	FileInputStream in = null;
	ByteArrayOutputStream buffer = null;
	try {
		in = new FileInputStream(path);
		buffer = new ByteArrayOutputStream();
		while ((iRead = in.read()) != -1) {
			buffer.write(iRead);
		}
		return buffer.toByteArray();
	} finally {
		FileUtility.safelyCloseInputStream(in);
		FileUtility.safelyCloseOutputStream(buffer);
	}
}

 检测类变化的方法

  判断类创建的时间如变化则重新加载

/**
* 保存类路径和时间
*/
private static Map mapModify = new HashMap();

private boolean hasChanged(Class c) throws IOException {
	boolean isChanged = false;
	String path = c.getResource(c.getSimpleName() + ".class").getPath();
	File f = new File(path);
	if (f.exists()) {
		Date newDate = new Date(f.lastModified());
		Date oldDate = null;
		String key = f.getCanonicalPath();
		if (mapModify.containsKey(key)) {
			oldDate = (Date) mapModify.get(key);
		} else {
			oldDate = firstDate;
		}
		isChanged = oldDate.compareTo(newDate) < 0;
		if (isChanged) {
			mapModify.put(key, newDate);
		}
	}
	return isChanged;
}

 

检测类变化的时机

创建类时检查

/**
 * 如果class文件重新生成过会自动加载
 * 
 * @param name
 * @return
 * @throws InstantiationException
 * @throws IllegalAccessException
 * @throws ClassNotFoundException
 * @throws IOException
 */
public Object getInstance(String name) throws InstantiationException,
		IllegalAccessException, ClassNotFoundException, IOException {
	Class c = Class.forName(name);
	Class cNew = reloadClass(c);
	if (cNew == null) {
		cNew = c;
	}
	Object o = cNew.newInstance();
	return o;
}

public synchronized Class reloadClass(Class c) throws IOException {
	Class cNew = null;
	if (hasChanged(c)) {
		cNew = loadClass(c);
	}
	return cNew;
}

 调用方法时检查

创建代理对象,自定义方法拦截器,intercept为方法拦截器中的一个方法如下

private Object target;

/**
 * 在调用类时判断该类是否重新编译过,如编译过则调用新类的方法
 */
public Object invoke(Object proxy, Method method, Object[] args)
		throws Throwable {
	Object targetObject = null;
	Class c = target.getClass();
	Class cNew = manager.reloadClass(c);
	if (cNew == null) {
		targetObject = target;
	} else {
		targetObject = cNew.newInstance();
		this.setTarget(targetObject);
	}
	Object returnValue = method.invoke(targetObject, args);
	return returnValue;
}


public void setTarget(Object target) {
	this.target = target;
}

 如何更新原来已创建的对象

当class发生改变时可以更新生成新的Class类从而创建新的对象,但是原来已创建的对象怎么办呢,可以用委托实现,代码同上,此处拦截类其实是个委托类,他把方法转给target对象,并在类更新时重新定义target的引用,而从外部调用看是感觉不到这点的。

 

所有类代码

package com.hrtc.dynamic.hot;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import net.sf.cglib.proxy.Enhancer;

import com.hrtc.dynamic.proxy.DynamicProxyFactory;
import com.hrtc.test.ITest;
import com.hrtc.test.Test;

/**
 * 创建可以动态更新的java对象<br>
 * 限制:构造函数不能有参数
 * 必须实现一个接口
 * 只能有实例方法(因为接口中不能有静态方法)
 * @author xuwei
 * Jul 9, 2008 12:01:00 PM
 */
public class ClassManager {
	/**
	 * 保存类路径和时间
	 */
	private static Map mapModify = new HashMap();
	/**
	 * 该类被加载时的时间
	 */
	private static Date firstDate = new Date();

	/**
	 * 如果class文件重新生成过会自动加载,只有重新创建才会更新
	 * 
	 * @param name
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public Object getInstance(String name) throws InstantiationException,
			IllegalAccessException, ClassNotFoundException, IOException {
		Class c = Class.forName(name);
		Class cNew = reloadClass(c);
		if (cNew == null) {
			cNew = c;
		}
		Object o = cNew.newInstance();
		return o;
	}

	/**
	 * 创建代理对象,如果class文件重新生成过会自动加载,调用原先以实例化的方法时也会更新类
	 * 
	 * @param name
	 * @return
	 * @throws InstantiationException
	 * @throws IllegalAccessException
	 * @throws ClassNotFoundException
	 * @throws IOException
	 */
	public Object getInstanceProxy(String name) throws InstantiationException,
			IllegalAccessException, ClassNotFoundException, IOException {
		Object target = getInstance(name);

		DynamicProxyFactory factory = new DynamicProxyFactory(
				new HotInvocationHandler(this));

		return factory.newProxyInstance(target);
	}

	/**
	 * 重新加载类
	 * 
	 * @param c
	 * @return
	 * @throws IOException
	 */
	public synchronized Class reloadClass(Class c) throws IOException {
		Class cNew = null;
		if (hasChanged(c)) {
			cNew = loadClass(c);
		}
		return cNew;
	}

	private boolean hasChanged(Class c) throws IOException {
		boolean isChanged = false;
		String path = c.getResource(c.getSimpleName() + ".class").getPath();
		File f = new File(path);
		if (f.exists()) {
			Date newDate = new Date(f.lastModified());
			Date oldDate = null;
			String key = f.getCanonicalPath();
			if (mapModify.containsKey(key)) {
				oldDate = (Date) mapModify.get(key);
			} else {
				oldDate = firstDate;
			}
			isChanged = oldDate.compareTo(newDate) < 0;
			if (isChanged) {
				mapModify.put(key, newDate);
			}
		}
		return isChanged;
	}

	private Class loadClass(Class c) throws IOException {
		ClassLoaderAdvisor classLoader = new ClassLoaderAdvisor();
		Class cNew = classLoader.loadClass(c);
		return cNew;
	}

	public static void main(String[] args) throws IOException,
			InstantiationException, IllegalAccessException,
			ClassNotFoundException, InterruptedException {
		ClassManager manager = new ClassManager();
		String className = "com.hrtc.test.Test";
		ITest t = (ITest) manager.getInstanceProxyJAVA(className);
		t.test();

		int size = 5;
		int i = 0;
		while (i < size) {
			System.out.println(i);
			i++;
			Thread.currentThread().sleep(1000);
		}

		t.test();

		i = 0;
		while (i < size) {
			System.out.println(i);
			i++;
			Thread.currentThread().sleep(1000);
		}

		t.test();
		
		long beginTime1 = System.currentTimeMillis();
		int count = 10000;
		for(int k = 0;k < count;k++){
			t.test();
		}
		long endTime1 = System.currentTimeMillis();
		
		
		ITest t2 = new Test();
		long beginTime2 = System.currentTimeMillis();
		for(int k = 0;k < count;k++){
			t2.test();
		}
		long endTime2 = System.currentTimeMillis();
		
		System.out.println("proxy time======="+(endTime1-beginTime1));
		System.out.println("no proxy time======="+(endTime2-beginTime2));
		

	}
}

 

package com.hrtc.dynamic.hot;

import java.lang.reflect.Method;

import com.hrtc.dynamic.proxy.DefaultInvocationHandler;

/**
 * 拦截java方法,更新新的类
 * @author xuwei
 * Jul 9, 2008 12:02:26 PM
 */
public class HotInvocationHandler extends DefaultInvocationHandler {

	private ClassManager manager;

	public HotInvocationHandler(ClassManager manager) {
		this.manager = manager;
	}

	/**
	 * 在调用类时判断该类是否重新编译过,如编译过则调用新类的方法
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object targetObject = null;
		Class c = target.getClass();
		Class cNew = manager.reloadClass(c);
		if (cNew == null) {
			targetObject = target;
		} else {
			targetObject = cNew.newInstance();
			this.setTarget(targetObject);
		}
		Object returnValue = method.invoke(targetObject, args);
		return returnValue;
	}

}
 
package com.hrtc.dynamic.hot;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

import com.hrtc.util.FileUtility;

public class ClassLoaderAdvisor extends ClassLoader {
	public ClassLoaderAdvisor() {
	}

	public ClassLoaderAdvisor(ClassLoader parentLoader) {
		super(parentLoader);
	}

	/**
	 * 加载某个类
	 * @param c
	 * @return
	 * @throws IOException
	 */
	@SuppressWarnings( { "unchecked" })
	public Class loadClass(Class c) throws IOException {
		byte[] bs = loadByteCode(c);
		Class cNew = super.defineClass(c.getCanonicalName(), bs, 0, bs.length);
		return cNew;
	}

	/**
	 * 加载某个类的字节码
	 * @param c
	 * @return
	 * @throws IOException
	 */
	private byte[] loadByteCode(Class c) throws IOException {
		int iRead = 0;
		String path = c.getResource(c.getSimpleName() + ".class").getPath();

		FileInputStream in = null;
		ByteArrayOutputStream buffer = null;
		try {
			in = new FileInputStream(path);
			buffer = new ByteArrayOutputStream();
			while ((iRead = in.read()) != -1) {
				buffer.write(iRead);
			}
			return buffer.toByteArray();
		} finally {
			FileUtility.safelyCloseInputStream(in);
			FileUtility.safelyCloseOutputStream(buffer);
		}
	}
}
 
package com.hrtc.dynamic.proxy;

import java.lang.reflect.Proxy;

/**
 * java代理工厂实现
 * @author xuwei
 * Jul 9, 2008 12:02:48 PM
 */
public class DynamicProxyFactory {
	/*
	 * 方法处理者
	 */
	private DefaultInvocationHandler invocationHandler;

	public DynamicProxyFactory() {
		this(null);
	}

	/**
	 * 
	 * @param invocationHandler
	 */
	public DynamicProxyFactory(DefaultInvocationHandler invocationHandler) {
		if (invocationHandler == null) {
			this.invocationHandler = new DefaultInvocationHandler();
		} else {
			this.invocationHandler = invocationHandler;
		}
	}

	/**
	 * 创建代理对象
	 * @param target
	 * @return
	 */
	public Object newProxyInstance(final Object target) {
		invocationHandler.setTarget(target);
		Object proxy = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				invocationHandler);
		return proxy;
	}
}
 
package com.hrtc.dynamic.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 默认代理处理类
 * @author xuwei
 * Jul 9, 2008 12:03:00 PM
 */
public class DefaultInvocationHandler implements InvocationHandler {

	/**
	 * 目标对象
	 */
	protected Object target;

	public DefaultInvocationHandler() {
	
	}

	/**
	 * 处理方法
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("before invoke");
		Object returnValue = method.invoke(target, args);
		System.out.println("after invoke");
		return returnValue;
	}

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}
}
 
package com.hrtc.test;

public interface ITest {
	void test();
}

 

package com.hrtc.test;

public class Test implements ITest {
	public void test() {
		System.out.println("call test method:modify here");
	}
}

 

目前为止上述讲到的限制没有解决,不知道有没有解决方案?

本文参考了http://www.javaworld.com/javaworld/jw-06-2006/jw-0612-dynamic.html?page=1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值