java代理-javassist

jopen 10年前

javaAgent是从JDK1.5及以后引入的,在1.5之前无法使用,也可以叫做java代理。

      代理 (agent) 是在你的main方法前的一个拦截器 (interceptor),也就是在main方法执行之前,执行agent的代码。agent的代码与你的main方法在同一个JVM中运行,并被同一个system classloader装载,被同一的安全策略 (security policy) 和上下文 (context) 所管理。

在java5和java6中只需要实现premain这个方法:

package monitor;    import java.lang.instrument.Instrumentation;    public class MyAgent {     public static void premain(String agentArgs, Instrumentation inst) {    inst.addTransformer(new MonitorTransformer());   }  }

premain方法的参数里有一个Instrumentation,使用instrumentation开发者可以构建独立于应用程序的java agent(代理)程序,用来监测运行在JVM上的程序,甚至可以动态的修改和替换类的定义。给力的说,这种方式相当于在JVM级别做了AOP支持,这样我们可以在不修改应用程序的基础上就做到了AOP.你不必去修改应用程序的配置,也不必重新打包部署验证。

JDK5中只能通过命令行参数在启动JVM时指定javaagent参数来设置代理类,而JDK6中已经不仅限于在启动JVM时通过配置参数来设置代理类,JDK6中通过 Java Tool API 中的 attach 方式,我们也可以很方便地在运行过程中动态地设置加载代理类,以达到 instrumentation 的目的
Instrumentation 的最大作用,就是类定义动态改变和操作

最简单的一个例子,计算某个方法执行需要的时间,不修改源代码的方式,使用Instrumentation 代理来实现这个功能。

建立一个 Transformer 类:MonitorTransformer 

这个类实现了接口public interface ClassFileTransformer实现这个接口的目的就是在class被装载到JVM之前将class字节码转换掉,从而达到动态注入代码的目的。那么首先要了解MonitorTransformer 这个类的目的,就是对想要修改的类做一次转换,这个用到了javassist对字节码进行修改,可以暂时不用关心jaavssist的原理,用ASM同样可以修改字节码,只不过比较麻烦些。

package monitor;    import java.lang.instrument.ClassFileTransformer;  import java.lang.instrument.IllegalClassFormatException;  import java.security.ProtectionDomain;  import java.util.ArrayList;  import java.util.List;    import javassist.ClassPool;  import javassist.CtClass;  import javassist.CtMethod;  import javassist.CtNewMethod;    public class MonitorTransformer implements ClassFileTransformer {     final static String prefix = "\nlong startTime = System.currentTimeMillis();\n";   final static String postfix = "\nlong endTime = System.currentTimeMillis();\n";   final static List<String> methodList = new ArrayList<String>();     public MonitorTransformer() {    methodList.add("main.TimeTest.sayHello");    methodList.add("main.TimeTest.sayHello2");   }     @Override   public byte[] transform(ClassLoader loader, String className,     Class<?> classBeingRedefined, ProtectionDomain protectionDomain,     byte[] classfileBuffer) throws IllegalClassFormatException {    if (className.startsWith("main")) {//判断加载的class的包路径是不是需要监控的类     className = className.replace("/", ".");     CtClass ctclass = null;     try {      ctclass = ClassPool.getDefault().get(className);//使用全称,用于取得字节码类<使用javassist>      for (String method : methodList) {       if (method.startsWith(className)) {        String methodName = method.substring(          method.lastIndexOf('.') + 1, method.length());          String outputStr = "\nSystem.out.println(\"this method "          + methodName          + " cost:\" +(endTime - startTime) +\"ms.\");";          CtMethod ctmethod = ctclass          .getDeclaredMethod(methodName);//得到这方法实例          String newMethodName = methodName + "$impl";//新定义一个方法叫做比如sayHello$impl           ctmethod.setName(newMethodName);//原来的方法改个名字             CtMethod newMethod = CtNewMethod.copy(ctmethod,          methodName, ctclass, null);//创建新的方法,复制原来的方法 ,名字为原来的名字          //构建新的方法体          StringBuilder bodyStr = new StringBuilder();        bodyStr.append("{");        bodyStr.append(prefix);        bodyStr.append(newMethodName + "($$);\n");//调用原有代码,类似于method();($$)表示所有的参数           bodyStr.append(postfix);        bodyStr.append(outputStr);        bodyStr.append("}");          newMethod.setBody(bodyStr.toString());//替换新方法           ctclass.addMethod(newMethod);//增加新方法          }      }      return ctclass.toBytecode();     } catch (Exception e) {      e.printStackTrace();     }    }    return null;   }  }

代码结构:
java代理-javassist

Manifest-Version: 1.0  Premain-Class: monitor.MyAgent  Can-Redefine-Classes: true  Boot-Class-Path: javassist.jar

注意有一行空格


下面把代理打成一个jar包
java代理-javassist
导出的时候注意:将MANIFEST.MF打包进去

导出的jar放入:D:\javaagentTest\agentMethod.jar  供后面测试使用,将javassist.jar也放入相同路径

测试代码:
package main;    public class TimeTest {     public static void main(String[] args) {    sayHello();    sayHello2("hello world222222222");   }     public static void sayHello() {    try {     Thread.sleep(2000);     System.out.println("hello world!!");    } catch (InterruptedException e) {     e.printStackTrace();    }   }     public static void sayHello2(String hello) {    try {     Thread.sleep(1000);     System.out.println(hello);    } catch (InterruptedException e) {     e.printStackTrace();    }   }  }
测试代码在运行的时候加上VM arguments: -javaagent:D:\javaagentTest\agentMethod.jar

hello world!!  this method sayHello cost:2000ms.  hello world222222222  this method sayHello2 cost:1000ms.

来自:http://my.oschina.net/OutOfMemory/blog/309283
参考:http://blog.csdn.net/qyongkang/article/details/7765255