使用 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 { Listlist = 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个类实现简单的热替换
源码地址:
下面进行简单的测试:
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文件
运行结果:
总结:总体来说可以实现一些简单的需求,也只限于对已有的方法进行修改,对于整个类的结构修改,仍然需要重启虚拟机