1、原理:基于JAVAAgent和Java字節碼注入技術的java探針工具技術原理
2、原理分析
動態代理功能實現說明,我們利用javaAgent和ASM字節碼技術開發java探針工具,實現原理如下:
jdk1.5以后引入了javaAgent技術,javaAgent是運行方法之前的攔截器。我們利用javaAgent和ASM字節碼技術,在JVM加載class二進制文件的時候,利用ASM動態的修改加載的class文件,在監控的方法前后添加計時器功能,用于計算監控方法耗時,同時將方法耗時及內部調用情況放入處理器,處理器利用棧先進后出的特點對方法調用先后順序做處理,當一個請求處理結束后,將耗時方法軌跡和入參map輸出到文件中,然后根據map中相應參數或耗時方法軌跡中的關鍵代碼區分出我們要抓取的耗時業務。最后將相應耗時軌跡文件取下來,轉化為xml格式并進行解析,通過瀏覽器將代碼分層結構展示出來,方便耗時分析,如圖下圖所示。
Java探針工具功能點:
1、支持方法執行耗時范圍抓取設置,根據耗時范圍抓取系統運行時出現在設置耗時范圍的代碼運行軌跡。
2、支持抓取特定的代碼配置,方便對配置的特定方法進行抓取,過濾出關系的代碼執行耗時情況。
3、支持App層入口方法過濾,配置入口運行前的方法進行監控,相當于監控特有的方法耗時,進行方法專題分析。
4、支持入口方法參數輸出功能,方便跟蹤耗時高的時候對應的入參數。
5、提供WEB頁面展示接口耗時展示、代碼調用關系圖展示、方法耗時百分比展示、可疑方法凸顯功能。
3、實例:
JavaAgent 是JDK 1.5 以后引入的,也可以叫做Java代理。
JavaAgent 是運行在 main方法之前的攔截器,它內定的方法名叫 premain ,也就是說先執行 premain 方法然后再執行 main 方法。
查看原作者實例地址:
https://www.cnblogs.com/aspirant/p/8796974.html
JavaAgent 的應用場景
JDK5中只能通過命令行參數在啟動JVM時指定javaagent參數來設置代理類,而JDK6中已經不僅限于在啟動JVM時通過配置參數來設置代理類,JDK6中通過 Java Tool API 中的 attach 方式,我們也可以很方便地在運行過程中動態地設置加載代理類,以達到 instrumentation 的目的。 Instrumentation 的最大作用,就是類定義動態改變和操作。
最簡單的一個例子,計算某個方法執行需要的時間,不修改源代碼的方式,使用Instrumentation 代理來實現這個功能,給力的說,這種方式相當于在JVM級別做了AOP支持,這樣我們可以在不修改應用程序的基礎上就做到了AOP。
- 創建一個 ClassFileTransformer 接口的實現類 MyTransformer 實現 ClassFileTransformer 這個接口的目的就是在class被裝載到JVM之前將class字節碼轉換掉,從而達到動態注入代碼的目的。那么首先要了解MonitorTransformer 這個類的目的,就是對想要修改的類做一次轉換,這個用到了javassist對字節碼進行修改,可以暫時不用關心jaavssist的原理,用ASM同樣可以修改字節碼,只不過比較麻煩些。
- 代碼:
package com.shanhy.demo.agent;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
/**
* 檢測方法的執行時間
*
*/
public class MyTransformer implements ClassFileTransformer {
final static String prefix = "nlong startTime = System.currentTimeMillis();n";
final static String postfix = "nlong endTime = System.currentTimeMillis();n";
// 被處理的方法列表
final static Map<String, List<String>> methodMap = new HashMap<String, List<String>>();
public MyTransformer() {
add("com.shanhy.demo.TimeTest.sayHello");
add("com.shanhy.demo.TimeTest.sayHello2");
}
private void add(String methodString) {
String className = methodString.substring(0, methodString.lastIndexOf("."));
String methodName = methodString.substring(methodString.lastIndexOf(".") + 1);
List<String> list = methodMap.get(className);
if (list == null) {
list = new ArrayList<String>();
methodMap.put(className, list);
}
list.add(methodName);
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
className = className.replace("/", ".");
if (methodMap.containsKey(className)) {// 判斷加載的class的包路徑是不是需要監控的類
CtClass ctclass = null;
try {
ctclass = ClassPool.getDefault().get(className);// 使用全稱,用于取得字節碼類<使用javassist>
for (String methodName : methodMap.get(className)) {
String outputStr = "nSystem.out.println("this method " + methodName
+ " cost:" +(endTime - startTime) +"ms.");";
CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);// 得到這方法實例
String newMethodName = methodName + "$old";// 新定義一個方法叫做比如sayHello$old
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) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
return null;
}
}
復制