使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
网上的基于Instrumentation的说明有很多,不想做过多的介绍,下面基于项目中遇到的一些需求,做的一个简单的例子。
做的项目是一个手机网络游戏,游戏的业务变更性很强,特别是项目开发中以及项目上线后的前期,有些东西变更很少,但对游戏来说很重要,比如掉率,或者一些其他的数值影响,这对程序来说可能就是该一个数值,这种情况下如果去重启服务器有点得不偿失,如果能进行热替换就好了。下面看一个定时检查指定文件夹下需要变更的文件,然后进行热替换。
1.首先我们需要提供一个有premain方法的类
package com.hotpatch;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
/**
* 热替换
*
* @author ksfzhaohui
*
*/
public class HotPatch {
private final static Logger logger = Logger.getLogger(HotPatch.class);
public static void premain(String agentArgs, Instrumentation inst) {
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new HotPatchThread(inst), 5, 5, TimeUnit.SECONDS);
logger.info("hotPatch starting...");
}
}
提供了一个premain的方法,并且启动了一个定时器,定时执行HotPatchThread线程。
2.HotPatchThread线程
package com.hotpatch;
import java.io.File;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.List;
import org.apache.log4j.Logger;
/**
* 热替换线程
*
* @author ksfzhaohui
*
*/
public class HotPatchThread implements Runnable {
private final static Logger logger = Logger.getLogger(HotPatchThread.class);
private static final String ROOT_PATH = "hotfiles";
private Instrumentation inst;
public HotPatchThread(Instrumentation inst) {
this.inst = inst;
}
public void run() {
try {
List<File> list = FileUtil.readfile(ROOT_PATH);
if (list != null && list.size() > 0) {
for (File file : list) {
Class<?> clazz = Class.forName(getPackageName(file));
byte[] array = FileUtil.getBytesFromFile(file.getPath());
ClassDefinition def = new ClassDefinition(clazz, array);
inst.redefineClasses(def);
file.delete();
logger.info("hotpatch " + file.getPath() + " success");
}
}
} catch (Exception e) {
logger.error("hotpatching error", e);
}
}
/**
* 获取类的包名+类名
*
* @param file
* @return
*/
private String getPackageName(File file) {
String path = file.getPath();
int index = path.indexOf(ROOT_PATH);
path = path.substring(index + ROOT_PATH.length() + 1);
path = path.split("\\.")[0];
path = path.replaceAll("\\\\", ".");
return path;
}
}
HotPatchThread线程定时读取hotfiles路径下的需要更新的文件,然后进行热替换,
注:包名需要以文件夹的形式存在
下面看一下代码结构:
结构很简单,就3个类实现简单的热替换
源码地址:https://github.com/ksfzhaohui/hotpatch.git
下面进行简单的测试:
package agentTest;
public class AgentTest {
public static void main(String[] args) throws InterruptedException {
TClass c = new TClass();
while (true) {
System.out.println(c.getNumber());
Thread.sleep(1000);
}
}
}
每隔一秒读取一下number
package agentTest;
public class TClass {
private int k = 10;
public int getNumber() {
return k + 4;
}
}
将程序打成jar包例如叫test.jar,然后可以写一个bat文件:run.bat
java -javaagent:hotpatch-0.0.1-SNAPSHOT.jar -cp test.jar agentTest.AgentTest
下面修改一下TClass文件,把k+4改成k+5,编译成class文件,放在hotfiles文件夹下
hotfiles文件夹下有一个agentTest文件夹(TClass的包名),agentTest下面是改变过的TClass.class文件
运行结果:
总结:总体来说可以实现一些简单的需求,也只限于对已有的方法进行修改,对于整个类的结构修改,仍然需要重启虚拟机