利用instrument+Attach API+javassist动态改变方法逻辑
2013-02-07 09:43
447 查看
1 instrument
instrument是jdk 1.5之后提供的一个功能,它通过代理的方式运行在 JVM 上的程序的服务。作为代理的类必须首先打成jar包。在jdk1.6中支持两种方式来启动代理:
(1) 在程序启动的时候添加-javaagent:jarpath=options
参数指定代理的jar来启动代理,这种情况下
代理入口类通过在META-INF/MENIFEST.MF清单文件中的Premain-Class属性指定,代理入口类必须实现以下两个函数之一(两个方法同时存在时优先调用第一个):
public static void premain(String agentArgs, Instrumentation inst);
或
public static void premain(String agentArgs);
(2)当目标程序已经在运行了,这种情况就只能采用第二种方式了。
首先必须在META-INF/MENIFEST.MF清单文件中通过Agent-Class属性来指定代理入口类。同样,代理入口类必须是实现以下两个函数之一(两个方法同时存在时优先调用第一个):
public static void agentmain(String agentArgs, Instrumentation inst);
或
public static void agentmain(String agentArgs);
具体内容可以参考http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html中的描述
这里需要介绍一下Instrumentation类,此类提供检测 Java 编程语言代码所需的服务。检测是向方法中添加字节码,以搜集各种工具所使用的数据。通过它,我们才能操作JVM,并修改Class中的一些内容。
这个类中有两个addTransformer方法,
void addTransformer(ClassFileTransformer transformer, boolean canRetransform)
该方法向Instrumentation注册一个转换类文件转换器。
ClassFileTransformer是一个接口,提供了一个
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException
该方法实现可以转换提供的类文件,并返回一个新的替换类文件。若不想替换类文件,可以返回null。
第二种方式显然已经不能再通过java命令的启动参数来指定了,这里就需要用到Attach API
2 Attach API
The Attach API
这篇文章中介绍得很详细了。
3.javassist
javassist是一个修改字节码创建Java字节码的类库。比asm工具容易上手。
4.动态改变类的行为
在运行的程序中出现问题时,有时候我们不希望停止程序来排查问题,这个时候可以通过动态的改变运行中的某些类的行为,或者打印某个值来排查。这个时候就需要我们能在程序运行过程中动态的改变类的行为。通过上面三种工具的结合,可以解决这个问题。
首先我们给出运行的目标程序:
public class HelloServiceImpl{ @Override public void sayHello() { System.out.println("hello"); } }
public class Client { /** * @param args */ public static void main(String[] args) { HelloServiceImpl service = new HelloServiceImpl(); while (true) { service.sayHello(); try { synchronized (service) { service.wait(3000); } } catch (InterruptedException e) { e.printStackTrace(); } } } }
这里Client每隔3秒钟调用一次sayHello方法。输出结果为hello.
现在想在hello输出前再输出方法的名字。
agent:
public class MyAgent { public static String className="com.alibaba.study.thread.HelloServiceImpl"; public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException { Class[] allClass = inst.getAllLoadedClasses(); for (Class c : allClass) { if(c.getName().equals(className)){ System.out.println("agent loaded"); inst.addTransformer(new DynamicClassTransformer(), true); inst.retransformClasses(c); } } } } public class DynamicClassTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { try { CtClass ctClass = ClassPool.getDefault().get("com.alibaba.study.thread.HelloServiceImpl"); String methodName = "sayHello"; CtMethod ctMethod=ctClass.getDeclaredMethod(methodName); System.out.println(ctMethod.getName()); ctMethod.insertBefore("System.out.println(\" sayHello\");"); ctClass.writeFile(); return ctClass.toBytecode(); } catch (Exception e) { System.out.println(e.getMessage()); } return null; } }
然后在配置清单中指定Agent-Class属性为MyAgent,并打成jar包。
将agent attach到指定的java虚拟机进程上。
public class AttachMain { public static void main(String args[]) throws AttachNotSupportedException{ VirtualMachine vm; List<VirtualMachineDescriptor> vmList= VirtualMachine.list(); if(vmList!=null){ for(int i=0;i<vmList.size();i++){ System.out.println("["+i+"] "+vmList.get(i).displayName()+" ,id:"+vmList.get(i).id()+" ,provider:"+vmList.get(i).provider()); } try{ int num=System.in.read()-48; if(num!=-1&&num<vmList.size()){ vm= VirtualMachine.attach(vmList.get(num)); vm.loadAgent("/home/tanfeng/myagent.jar"); System.in.read(); } }catch(Exception e){ e.printStackTrace(); } } } }
这样,当agent被加载之后,就可以看到在输出hello之前,会输出sayHello这个字符串。
这种功能相当强大,我们可以用它来做些其它用途。
当然我们没有必要自己去写这些程序了,因为已经有人帮我们做了,Btrace
就是这样一款工具。
相关文章推荐
- ASM(四) 利用Method 组件动态注入方法逻辑
- ASM(四) 利用Method 组件动态注入方法逻辑
- ASM(六) 利用TreeApi 动态生成以及转换方法字节码
- 字节码操作_javassist库_动态创建新类_属性_方法_构造器_API详解JAVA216-217
- 关于Highcharts图表组件动态修改属性的方法(API)总结之Axis
- 关于Highcharts图表组件动态修改属性的方法(API)总结之Axis
- 整理一下Java动态编译Java代码,并在加载到内存中然后执行类中方法的api的介绍
- <table>标签 利用DOM 的方法和属性实现对表格的动态操作
- 动态构造 Java 类的一种方法: javassist (3)
- 文章列表正序、反序排列——利用JS动态改变跳转网址
- 动态改变窗口图标的方法 WM_SETICON消息
- 利用自定义ClassLoader和接口逻辑后台可刷新缓存实现java-web项目的动态发布
- JAVAWEB开发之Servlet3.0新特性的使用以及注解的详细使用和自定义注解的方法、动态代理的使用、利用动态代理实现细粒度的权限控制以及类加载和泛型反射
- 一个利用扩展方法的实例:AttachDataExtensions
- 利用反射来动态创建实例和调用方法(上)
- 利用reloadRowsAtIndexPaths:withRowAnimation:来动态改变cell的高度
- 完美解决json循环问题(使用javassist增强):Spring MVC中使用jackson的MixInAnnotations方法动态过滤JSON字段
- gradle学习二 利用javassist api修改class字节码
- 利用Objective-C的反射机制和运行时特性实现类静态方法的动态访问(一)
- python利用字典保存配置实现动态调用模块类方法