埋點實作在方法前後動态插入代碼,擷取方法的執行時間。
常見的方法有以下3鐘:
1 寫死
2 spirng aop 動态代理
3 動态插入位元組碼
其中 1 和 2 系統代碼侵入性大,方法3不用更改系統代碼。
javaAgent技術
JavaAgent是從JDK1.5及以後引入的,在1.5之前無法使用,也可以叫做java代理。利用 java代理,即 java.lang.instrument 做動态 Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能從本地代碼中解放出來,使之可以用 Java 代碼的方式解決問題。
使用 Instrumentation,開發者可以建構一個獨立于應用程式的代理程式(Agent),用來監測和協助運作在 JVM 上的程式,甚至能夠替換和修改某些類的定義。有了這樣的功能,開發者就可以實作更為靈活的運作時虛拟機監控和 Java 類操作了,這樣的特性實際上提供了一種虛拟機級别支援的 AOP 實作方式,使得開發者無需對 JDK 做任何更新和改動,就可以實作某些 AOP 的功能了。在 Java SE 6 裡面,instrumentation 包被賦予了更強大的功能:啟動後的 instrument、本地代碼(native code)instrument,以及動态改變 classpath 等等。這些改變,意味着 Java 具有了更強的動态控制、解釋能力,它使得 Java 語言變得更加靈活多變。Instrumentation 的最大作用,就是類定義動态改變和操作。
開發者可以在一個普通 Java 程式(帶有 main 函數的 Java 類)運作時,通過 -javaagent參數指定一個特定的 jar 檔案(包含 Instrumentation 代理)來啟動 Instrumentation 的代理程式。開發者可以讓 Instrumentation 代理在 main 函數運作前執行premain函數。
基本步驟:
1 編寫premian函數
2 将監控程式打包jar,META-INF/MAINIFEST.MF 必須包含 Premain-Class
3 使用java -javaagent:jar 檔案的位置 [= 傳入 premain 的參數 ]運作被監控的程式
建立項目JAgent
1 增加pom依賴
<dependency>
<groupId>jboss</groupId>
<artifactId>javassist</artifactId>
<version>3.8.0.GA</version>
</dependency>
2 編寫permian函數
import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class JAgent {
public static void main(String[] args) {
System.out.println("main");
}
/**
* 在這個 premain 函數中,開發者可以進行對類的各種操作。
* @param agentOps
* agentArgs 是 premain 函數得到的程式參數,随同 “– javaagent”一起傳入。與 main 函數不同的是,
* 這個參數是一個字元串而不是一個字元串數組,如果程式參數有多個,程式将自行解析這個字元串。
* @param inst
* 是一個 java.lang.instrument.Instrumentation 的執行個體,
* 由 JVM 自動傳入。java.lang.instrument.Instrumentation 是 instrument 包中定義的一個接口,
* 也是這個包的核心部分,集中了其中幾乎所有的功能方法,例如類定義的轉換和操作等等。
*/
public static void premain(String agentOps, Instrumentation inst) {
System.out.println("premain:"+agentOps);
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
//判斷要埋點的類
if(!"com/chy/JSercice".equals(className)) {
return null;
}
try {
ClassPool classPool=new ClassPool();
classPool.insertClassPath(new LoaderClassPath(loader));
CtClass ctClass= classPool.get(className.replace("/","."));
CtMethod ctMethod= ctClass.getDeclaredMethod("run");
//插入本地變量
ctMethod.addLocalVariable("begin",CtClass.longType);
ctMethod.addLocalVariable("end",CtClass.longType);
ctMethod.insertBefore("begin=System.currentTimeMillis();System.out.println(\"begin=\"+begin);");
//前面插入:最後插入的放最上面
ctMethod.insertBefore("System.out.println( \"埋點開始-2\" );");
ctMethod.insertBefore("System.out.println( \"埋點開始-1\" );");
ctMethod.insertAfter("end=System.currentTimeMillis();System.out.println(\"end=\"+end);");
ctMethod.insertAfter("System.out.println(\"性能:\"+(end-begin)+\"毫秒\");");
//後面插入:最後插入的放最下面
ctMethod.insertAfter("System.out.println( \"埋點結束-1\" );");
ctMethod.insertAfter("System.out.println( \"埋點結束-2\" );");
return ctClass.toBytecode();
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
}
catch (IOException e){
e.printStackTrace();
}
return new byte[0];
}
});
}
}
3 打包jar
<build>
<plugins>
<!--編譯Java源碼-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<!-- 打成jar時,設定manifestEntries的參數 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<configuration>
<archive>
<manifestEntries>
<Project-name>${project.name}</Project-name>
<Project-version>${project.version}</Project-version>
<Premain-Class>com.chy.JAgent</Premain-Class>
<Can-Redefine-Classes>false</Can-Redefine-Classes>
</manifestEntries>
</archive>
<skip>true</skip>
</configuration>
</plugin>
<!-- 方法1: 包含所有依賴的jar檔案,依賴以class的方式存在 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.2.1</version>
<configuration>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.chy.JAgent</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<Premain-Class>com.chy.JAgent</Premain-Class> 指定 premain 函數入口
確定 META-INF/MAINIFEST.MF 必須包含 Premain-Class
建立項目JAgentTest (埋點項目)
public class App
{
public static void main( String[] args )
{
System.out.println( "JAgentTest is run" );
// run中埋點統計運作時間
new JSercice().run();
}
}
public class JSercice {
public void call() {
String name = "JSercice";
for (int j = 1; j <= 10000; j++) {
System.out.println(j);
}
System.out.println(name + " is end");
}
public void run() {
System.out.println("JSercice is start");
call();
}
}
配置項目 vm 參數
-javaagent:G:\java\intellij_idea\IdeaProjects\javaByteCode\JAgent\target\JAgent-1.0-SNAPSHOT.jar=JAgent
運作JAgentTest 列印結果如下
premain:JAgent
JAgentTest is run
埋點開始-1
埋點開始-2
begin=1530337378023
JSercice is start
..........
JSercice is end
end=1530337378024
性能:1毫秒
埋點結束-1
埋點結束-2