您的位置:首页 > 编程语言 > Java开发

利用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

就是这样一款工具。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐