近来需要完成一个feature:修改已load到JVM中的某个class,对其加一些代码,以此来动态修改运行中的程序。
对着这个feature我找到的方案是agent+Instrumentation+ASM
一路做下来有以下几点比较有意思:
1)动态attach agent到某个JVM进程
一般使用agent都是静态的,直接在运行某程序时加agent参数,这样agent会先于程序启动,这个不符合我的需求,我找到一个动态attach agent的方法,具体细节见以下代码:
- publicstaticvoidattach(Stringpid)throwsException{
- try{
- StringagentPath="/cutemock-agent.jar";
- Stringtmp=Main.class.getClassLoader().getResource("com/taobao/lp/cutemock/agent/Main.class").toString();
- tmp=tmp.substring(0,tmp.indexOf("!"));
- tmp=tmp.substring("jar:".length(),tmp.lastIndexOf("/"));
- agentPath=tmp+agentPath;
- agentPath=newFile(newURI(agentPath)).getAbsolutePath();
- VirtualMachinevm=null;
- if(debug){
- debugPrint("attachingto"+pid);
- }
- vm=VirtualMachine.attach(pid);
- if(debug){
- debugPrint("attachedto"+pid);
- }
- if(debug){
- debugPrint("loading"+agentPath);
- }
- StringagentArgs="port="+port;
- if(debug){
- agentArgs+=",debug=true";
- }
- if(debug){
- debugPrint("agentargs:"+agentArgs);
- }
- vm.loadAgent(agentPath,agentArgs);
- if(debug){
- debugPrint("loaded"+agentPath);
- }
- }catch(RuntimeExceptionre){
- throwre;
- }catch(IOExceptionioexp){
- throwioexp;
- }catch(Exceptionexp){
- throwexp;
- }
- }
这段代码的关键是要找到agent的jar包,然后通过VirtualMachine.attach和VirtualMachine.loadAgent把agent attach到pid上
2)通过Instrumentation修改已load了的class
见如下代码:
- Class[]classes=inst.getAllLoadedClasses();
- for(Classclazz:classes){
- if(clazz.getName().equals(CLASS_NAME)){
- System.out.println("addtransformertoTBRemotingRPCProtocolComponent.class");
- inst.addTransformer(newMyClassFileTransformer(),true);
- inst.retransformClasses(clazz);
- }
- }
关键在于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:
- mv.visitVarInsn(Opcodes.ALOAD,2);
- mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"com/taobao/hsf/model/metadata/ServiceMetadata","getUniqueName","()Ljava/lang/String;");
- mv.visitVarInsn(Opcodes.ALOAD,1);
- mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL,"com/taobao/hsf/domain/HSFRequest","getMethodName","()Ljava/lang/String;");
- mv.visitVarInsn(Opcodes.ALOAD,3);
- mv.visitMethodInsn(Opcodes.INVOKESTATIC,"com/taobao/lp/cutemock/agent/MockUtil","getTargetUrl","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
- mv.visitVarInsn(Opcodes.ASTORE,3);
- Labell4=newLabel();
- mv.visitLabel(l4);
但asm提供了一个eclipse plugin,更新地址为:http://andrei.gmxhome.de/eclipse/
它可以对比出修改前后的class的差异,并自动翻译为asm代码
以上是我这两天玩动态修改class的一些心得,有点乱,但确实是不断尝试后的心得