动态更改JVM里的class

来源:互联网 发布:数据库管理员简历 编辑:程序博客网 时间:2024/05/22 00:21

近来需要完成一个feature:修改已load到JVM中的某个class,对其加一些代码,以此来动态修改运行中的程序。

对着这个feature我找到的方案是agent+Instrumentation+ASM

一路做下来有以下几点比较有意思:

1)动态attach agent到某个JVM进程

一般使用agent都是静态的,直接在运行某程序时加agent参数,这样agent会先于程序启动,这个不符合我的需求,我找到一个动态attach agent的方法,具体细节见以下代码:

[java] view plaincopy
  1. public static void attach(String pid) throws Exception {  
  2.      try {  
  3.          String agentPath = "/cutemock-agent.jar";  
  4.          String tmp = Main.class.getClassLoader().getResource("com/taobao/lp/cutemock/agent/Main.class").toString();  
  5.          tmp = tmp.substring(0, tmp.indexOf("!"));  
  6.          tmp = tmp.substring("jar:".length(), tmp.lastIndexOf("/"));  
  7.          agentPath = tmp + agentPath;  
  8.          agentPath = new File(new URI(agentPath)).getAbsolutePath();  
  9.          VirtualMachine vm = null;  
  10.          if (debug) {  
  11.              debugPrint("attaching to " + pid);  
  12.          }  
  13.          vm = VirtualMachine.attach(pid);  
  14.          if (debug) {  
  15.              debugPrint("attached to " + pid);  
  16.          }  
  17.          if (debug) {  
  18.              debugPrint("loading " + agentPath);  
  19.          }  
  20.          String agentArgs = "port=" + port;  
  21.          if (debug) {  
  22.              agentArgs += ",debug=true";  
  23.          }  
  24.          if (debug) {  
  25.              debugPrint("agent args: " + agentArgs);  
  26.          }  
  27.          vm.loadAgent(agentPath, agentArgs);  
  28.          if (debug) {  
  29.              debugPrint("loaded " + agentPath);  
  30.          }  
  31.      } catch (RuntimeException re) {  
  32.          throw re;  
  33.      } catch (IOException ioexp) {  
  34.          throw ioexp;  
  35.      } catch (Exception exp) {  
  36.          throw exp;  
  37.      }  
  38.  }  

这段代码的关键是要找到agent的jar包,然后通过VirtualMachine.attach和VirtualMachine.loadAgent把agent attach到pid上

2)通过Instrumentation修改已load了的class

见如下代码:

[java] view plaincopy
  1. Class[] classes = inst.getAllLoadedClasses();  
  2.  for(Class clazz : classes){  
  3.      if(clazz.getName().equals(CLASS_NAME)){  
  4.          System.out.println("add transformer to TBRemotingRPCProtocolComponent.class");  
  5.          inst.addTransformer(new MyClassFileTransformer(),true);  
  6.          inst.retransformClasses(clazz);  
  7.      }  
  8.  }  

关键在于inst.addTransformer(new MyClassFileTransformer(),true);这个true参数,inst.retransformClasses(clazz);只会重新修改addTransformer中canRetransform==true的

3)通过asm eclipse plugin方便修改class

大家都知道可以通过asm来修改class,但其api及其难用,比如我仅仅只想加一行:

targetURL = MockUtil.getTargetUrl(metadata.getUniqueName(), request.getMethodName(), targetURL);

翻译为asm:

[java] view plaincopy
  1. mv.visitVarInsn(Opcodes.ALOAD, 2);  
  2. mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/taobao/hsf/model/metadata/ServiceMetadata""getUniqueName""()Ljava/lang/String;");  
  3. mv.visitVarInsn(Opcodes.ALOAD, 1);  
  4. mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "com/taobao/hsf/domain/HSFRequest""getMethodName""()Ljava/lang/String;");  
  5. mv.visitVarInsn(Opcodes.ALOAD, 3);  
  6. mv.visitMethodInsn(Opcodes.INVOKESTATIC, "com/taobao/lp/cutemock/agent/MockUtil""getTargetUrl""(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");  
  7. mv.visitVarInsn(Opcodes.ASTORE, 3);  
  8. Label l4 = new Label();  
  9. mv.visitLabel(l4);  

但asm提供了一个eclipse plugin,更新地址为:http://andrei.gmxhome.de/eclipse/

它可以对比出修改前后的class的差异,并自动翻译为asm代码

以上是我这两天玩动态修改class的一些心得,有点乱,但确实是不断尝试后的心得