目标:
当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