IDEA + maven 零基礎構建 java agent 項目
200316-IDEA + maven 零基礎構建 java agent 項目
Java Agent(java 探針)雖說在 jdk1.5 之後就有了,但是對於絕大多數的業務開發 javaer 來說,這個東西還是比較神奇和陌生的;雖說在實際的業務開發中,很少會涉及到 agent 開發,但是每個 java 開發都用過,比如使用 idea 寫了個 HelloWorld.java,並運行一下, 仔細看控制檯輸出
本篇將作爲 Java Agent 的入門篇,手把手教你搭建一個Java Agent開發測試項目環境
I. Java Agent 開發
首先明確我們的開發環境,選擇 IDEA 作爲編輯器,maven 進行包管理
1. 核心邏輯
創建一個新的項目(or 子 module),然後我們新建一個 SimpleAgent 類
public class SimpleAgent { /** * jvm 參數形式啓動,運行此方法 * * @param agentArgs * @param inst */ public static void premain(String agentArgs, Instrumentation inst) { System.out.println("premain"); } /** * 動態 attach 方式啓動,運行此方法 * * @param agentArgs * @param inst */ public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println("agentmain"); } }
我們先忽略上面兩個方法的具體玩法,先簡單看一下這兩個方法的區別,註釋上也說了
-
jvm 參數形式:調用 premain 方法
-
attach 方式:調用 agentmain 方法
其中 jvm 方式,也就是說要使用這個 agent 的目標應用,在啓動的時候,需要指定 jvm 參數 -javaagent:xxx.jar
,當我們提供的 agent 屬於基礎必備服務時,可以用這種方式
當目標應用程序啓動之後,並沒有添加 -javaagent
加載我們的 agent,依然希望目標程序使用我們的 agent,這時候就可以使用 attach 方式來使用(後面會介紹具體的使用姿勢),自然而然的會想到如果我們的 agent 用來 debug 定位問題,就可以用這種方式
2. 打包
上面一個簡單 SimpleAgent 就把我們的 Agent 的核心功能寫完了(就是這麼簡單),接下來需要打一個 Jar 包
通過 maven 插件,可以比較簡單的輸出一個合規的 java agent 包,有兩種常見的使用姿勢
a. pom 指定配置
在 pom.xml 文件中,添加如下配置,請注意一下 manifestEntries
標籤內的參數
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestEntries> <Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class> <Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> <executions> <execution> <goals> <goal>attached</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> </plugins> </build>
然後通過 mvn assembly:assembly
命令打包,在 target
目錄下,可以看到一個後綴爲 jar-with-dependencies
的 jar 包,就是我們的目標
b. MANIFEST.MF 配置文件
通過配置文件 MANIFEST.MF
,可能更加常見,這裏也簡單介紹下使用姿勢
-
在資源目錄(Resources)下,新建目錄
META-INF
-
在
META-INF
目錄下,新建文件MANIFEST.MF
文件內容如下
Manifest-Version: 1.0 Premain-Class: com.git.hui.agent.SimpleAgent Agent-Class: com.git.hui.agent.SimpleAgent Can-Redefine-Classes: true Can-Retransform-Classes: true
請注意,最後的一個空行(如果我上面沒有顯示的話,多半是 markdown 渲染有問題),不能少,在 idea 中,刪除最後一行時,會有錯誤提醒
然後我們的 pom.xml
配置,需要作出對應的修改
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifestFile> src/main/resources/META-INF/MANIFEST.MF </manifestFile> <!--<manifestEntries>--> <!--<Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class>--> <!--<Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class>--> <!--<Can-Redefine-Classes>true</Can-Redefine-Classes>--> <!--<Can-Retransform-Classes>true</Can-Retransform-Classes>--> <!--</manifestEntries>--> </archive> </configuration> <executions> <execution> <goals> <goal>attached</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> </plugins> </build>
同樣通過 mvn assembly:assembly
命令打包
II. Agent 使用
agent 有了,接下來就是需要測試一下使用 agent 的使用了,上面提出了兩種方式,我們下面分別進行說明
1. jvm 參數
首先新建一個 demo 項目,寫一個簡單的測試類
public class BaseMain { public int print(int i) { System.out.println("i: " + i); return i + 2; } public void run() { int i = 1; while (true) { i = print(i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { BaseMain main = new BaseMain(); main.run(); Thread.sleep(1000 * 60 * 60); } }
測試類中,有一個死循環,各 1s 調用一下 print 方法,IDEA 測試時,可以直接在配置類,添加 jvm 參數,如下
請注意上面紅框的內容爲上一節打包的 agent 絕對地址: -javaagent:/Users/..../target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar
執行 main 方法之後,會看到控制檯輸出
請注意上面的 premain
, 這個就是我們上面的 SimpleAgent
中的 premain
方法輸出,且只輸出了一次
2. attach 方式
在使用 attach 方式時,可以簡單的理解爲要將我們的 agent 注入到目標的應用程序中,所以我們需要自己起一個程序來完成這件事情
public class AttachMain { public static void main(String[] args) throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException { // attach方法參數爲目標應用程序的進程號 VirtualMachine vm = VirtualMachine.attach("36633"); // 請用你自己的agent絕對地址,替換這個 vm.loadAgent("/Users/......./target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar"); } }
上面的邏輯比較簡單,首先通過 jps -l
獲取目標應用的進程號
當上面的 main 方法執行完畢之後,控制檯會輸出類似下面的兩行日誌,可以簡單的理解爲我連上目標應用,並丟了一個 agent,然後揮一揮衣袖不帶走任何雲彩的離開了
Connected to the target VM, address: '127.0.0.1:63710', transport: 'socket' Disconnected from the target VM, address: '127.0.0.1:63710', transport: 'socket'
接下來再看一下上面的 BaseMain 的輸出,中間夾着一行 agentmain
, 就表明 agent 被成功注入進去了
3. 小結
本文介紹了 maven + idea 環境下,手把手教你開發一個 hello world 版 JavaAgent 並打包的全過程
兩個方法
方法 | 說明 | 使用姿勢 |
---|---|---|
premain() |
agent 以 jvm 方式加載時調用,即目標應用在啓動時,指定了 agent | -javaagent:xxx.jar |
agentmain() |
agent 以 attach 方式運行時調用,目標應用程序正常工作時使用 | VirtualMachine.attach(pid) 來指定目標進程號 vm.loadAgent("...jar") 加載 agent |
兩種打包姿勢
打包爲可用的 java agent 時,需要注意配置參數,上面提供了兩種方式,一個是直接在 pom.xml
中指定配置
<manifestEntries> <Premain-Class>com.git.hui.agent.SimpleAgent</Premain-Class> <Agent-Class>com.git.hui.agent.SimpleAgent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries>
另外一個是在配置文件 META-INF/MANIFEST.MF
中寫好(需要注意最後一個空行不可或缺)
Manifest-Version: 1.0 Premain-Class: com.git.hui.agent.SimpleAgent Agent-Class: com.git.hui.agent.SimpleAgent Can-Redefine-Classes: true Can-Retransform-Classes: true
當然本篇內容看完之後,會發現對 java agent 的實際開發還是不太清楚,難道 agent 就是在前面輸出一行 hello world
就完事了麼,這和想象中的完全不一樣啊
下一篇博文(明天推送...)將手把手教你實現一個方法統計耗時的 java agent 包,將詳細說明利用接口 Instrumentation
來實現字節碼修改,從而是實現功能增強
II. 其他
0. 源碼
-
https://github.com/liuyueyi/java-agent
1. 一灰灰 Blog:https://liuyueyi.github.io/hexblog
一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛
2. 聲明
盡信書則不如,以上內容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
-
微博地址: 小灰灰 Blog
-
QQ:一灰灰/3302797840