代理 (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