代理 (agent) 是在你的main方法前的一个拦截器 (interceptor),也就是在main方法执行以前,执行agent的代码。agent的代码与你的main方法在同一个JVM中运行,并被同一个system classloader装载,被同一的安全策略 (security policy) 和上下文 (context) 所管理。
在java5和java6中只须要实现premain这个方法:
java
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
app
这个类实现了接口public interface ClassFileTransformer。实现这个接口的目的就是在class被装载到JVM以前将class字节码转换掉,从而达到动态注入代码的目的。那么首先要了解MonitorTransformer 这个类的目的,就是对想要修改的类作一次转换,这个用到了javassist对字节码进行修改,能够暂时不用关心jaavssist的原理,用ASM一样能够修改字节码,只不过比较麻烦些。 ide
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; } }
代码结构:
post
Manifest-Version: 1.0 Premain-Class: monitor.MyAgent Can-Redefine-Classes: true Boot-Class-Path: javassist.jar
注意有一行空格 测试
下面把代理打成一个jar包
导出的时候注意:将MANIFEST.MF打包进去
导出的jar放入:D:\javaagentTest\agentMethod.jar 供后面测试使用,将javassist.jar也放入相同路径 ui
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://blog.csdn.net/qyongkang/article/details/7765255 this