日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

一个简单JavaAgent的实现

發布時間:2023/12/13 java 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一个简单JavaAgent的实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、什么是javaagent

javaagent是一個JVM“插件”,一種專門精心制作的.jar文件,它能夠利用JVM提供的Instrumentation API。

1.1、概要

Java Agent由三部分組成:代理類、代理類元信息和JVM加載.jar和代理的機制,整體內容如下圖所示:

1.2、javaagent的基石

java.lang.instrument 為javaagent 通過修改方法字節碼的方式操作運行在JVM上的程序提供服務。javaagent以JAR包的形式部署,JAR文件清單中的屬性指定要加載的代理類,以啟動代理。

javaagent的啟動方式有以下幾種:

  • 通過在命令行指定參數啟動。

  • JVM啟動后啟動。例如,提供一種工具,該工具可以依附到已運行的應用,并允許在已運行的應用內加載代理。

  • 與應用一起打包為可執行文件。

1.3、啟動 javaagent

1.3.1、命令行啟動

命令行啟動參數如下:

-javaagent:<jarpath>[=<options>]

<jarpath> :javaagent的路徑,比如 /opt/var/Agent-1.0.0.jar 。
<options> : javaagent參數,參數的解析由javaagent負責。
javaagent JAR文件清單必須包含 Premain-Class 屬性,屬性的值為agent class的全路徑名(包名+類名)。代理類必須實現 premain 方法,premain 方法和 main 方法一樣分別是代理和應用的入口點。JVM初始化完成后首先調用代理的premain函數,然后調用應用的main函數,premain方法必須返回后進程才能啟動。

premain 方法簽名如下:

public static void premain(String agentArgs, Instrumentation inst) public static void premain(String agentArgs)

JVM首先嘗試在代理中調用簽名為1的方法,如果代理類沒有實現簽名為1的方法,JVM嘗試調用簽名為2的方法:

代理類可以有一個 agentmain函數,函數會在JVM啟動完成之后調用。如果,使用命令行啟動代理,agentmain 方式不會被調用。

代理的所有參數被當作一個字符串通過 agentArgs 變量傳遞,代理負責解析參數字符串。

如果代理因為代理類無法被加載、代理類未實現 premain 方法或拋出了未被捕獲的異常,JVM將會退出。

javaagent的啟動不要求實現一定提供命令行的方式,如果,實現支持通過命令行啟動,實現必須支持在命令行中通過指定 -javaagent 參數啟動。 -javaagent 可以在命令行中使用多次,啟動多個代理。premain 函數的調用順序和命令行中指定的順序一致,多個代理可以使用相同 <jarpath>。

沒有一個嚴格模型來定義 premain 函數的工作范圍,任何 main 函數可以做的工作,比如創建線程,在 premain 函數中都是合法的。

1.3.2、JVM啟動后啟動

實現可以提供在JVM啟動之后再啟動代理的機制。代理如何啟動的細節特定于實現,通常應用程序已經啟動,并且它的 main 方法已經被調用。如果實現支持在JVM啟動后啟動代理,代理必須滿足以下條件:

  • 清單文件包含 Agent-Class 屬性,屬性的值為代理類全名。

  • 代理類必須實現 public static agentmain 方法。

agentmain方法有以下兩個函數簽名:

public static void agentmain(String agentArgs, Instrumentation inst) public static void agentmain(String agentArgs)

JVM首先嘗試調用具有簽名1的方法,如果,代理類沒有實現該方法,JVM嘗試調用簽名為2的方法。

代理類可以同時實現 premainagentmain 兩個方法,當代理以命令行方式啟動時,JVM調用 premain 函數,當代理在JVM啟動之后啟動時,JVM調用 agentmain 函數,而且JVM不會調用 premain 函數。

agentmain 函數參數的傳遞也是通過 agentArgs,所有參數組合為一個字符串,參數的解析由代理負責。

agentmain 函數必須完成啟動代理所有必須的初始化動作,當啟動完成后,agentmain 函數必須返回。如果,代理不能啟動或拋出未捕獲的異常,JVM都會退出。

1.3.3、打包為可執行文件

如果代理打包到可執行JAR文件中,可執行JAR文件的清單中必須包含 Launcher-Agent-Class 屬性,指定一個在應用main函數調用之前代理啟動的類。JVM嘗試在代理上調用以下方法:

public static void agentmain(String agentArgs, Instrumentation inst)

如果,代理類沒有實現上述方法,JVM則調用下面的方法。

public static void agentmain(String agentArgs)

agentArgs 參數的值必須為空字符串。

agentmain 函數必須完成代理啟動必須的所有初始化動作并在啟動后返回。如果,代理無法啟動或拋出未捕獲的異常,JVM會退出。

1.3.4、加載代理類以及代理類可用的模塊/類

系統類加載器負責加載代理JAR文件中的所有類,并且成為系統類加載器的未命名模塊的成員。 系統類加載器通常也定義包含應用程序 main 方法的類。對代理類可見的所有類都對系統類加載器可見,必須滿足下面的最低要求:

  • 啟動層中的模塊導出的包中的類。 啟動層是否包含所有平臺模塊取決于初始模塊或應用程序的啟動方式。

  • 類可被系統類加載器定義。

  • 啟動類加載器定義的所有代理的類為其未命名模塊的成員。

如果代理類需要鏈接到不在啟動層中的平臺(或其他)模塊中的類,則需要以確保這些模塊位于啟動層中的方式啟動應用程序。 例如,在JDK實現中,--add-modules 命令行選項可用于將模塊添加到要在啟動時解析的根模塊集中。

啟動類加載器可以加載代理支持的類(通過 appendToBootstrapClassLoaderSearch 或指定Boot-Class-Path屬性)必須僅鏈接到定義啟動類加載器的類。 無法保證啟動類加載器可以在所有平臺工作。

如果配置了自定義系統類加載器(通過 getSystemClassLoader 方法中指定的系統屬性 java.system.class.loader ),則必須定義 appendToSystemClassLoaderSearch 中指定的 appendToClassPathForInstrumentation 方法。 換句話說,自定義系統類加載器必須支持將代理JAR文件添加到系統類加載器搜索范圍內的機制。

1.4、javaagent清單屬性

屬性說明是否必選默認值
Premain-Class包含premain方法的類依賴啟動方式
Agent-Class包含agentmain方法的類依賴啟動方式
Boot-Class-Path啟動類加載器搜索路徑
Can-Redefine-Classis是否可以重定義代理所需的類false
Can-Retransform-Classis是否能夠重新轉換此代理所需的類false
Can-Set-Native-Method-Prefix是否能夠設置此代理所需的本機方法前綴false

二、寫一個Java Agent

基于上面的介紹,我們實現一個下載JVM中所有非系統類的javaagent。

整個開發過程包括以下三步:

  • 1)定義代理類,實現類下載功能;

  • 2)配置、打包;

  • 3)命令行啟動測試。

2.1、代理類實現

實現 premain 函數

package io.ct.java.agent;import java.lang.instrument.Instrumentation;public class AgentApplication {public static void premain(String arg, Instrumentation instrumentation) {System.err.println("agent startup , args is " + arg);// 注冊我們的文件下載函數instrumentation.addTransformer(new DumpClassesService());} }

文件下載類實現 ClassFileTransformer 接口,在類被加載時下載類的字節碼:

package io.ct.java.agent;import java.io.FileOutputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.Arrays; import java.util.List;/*** Copyright (C), 2018-2018, open source* FileName: DumpClassesService** @author : 大哥* Date: 2018/12/8 21:01*/ public class DumpClassesService implements ClassFileTransformer {private static final List<String> SYSTEM_CLASS_PREFIX = Arrays.asList("java", "sum", "jdk");@Overridepublic byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {if (!isSystemClass(className)) {System.out.println("load class " + className);FileOutputStream fos = null;try {// 將類名統一命名為classNamedump.class格式fos = new FileOutputStream(className + "dump.class");fos.write(classfileBuffer);fos.flush();} catch (IOException ioe) {ioe.printStackTrace();} finally {// 關閉文件輸出流if (null != fos) {try {fos.close();} catch (IOException e) {e.printStackTrace();}}}}return classfileBuffer;}/*** 判斷一個類是否為系統類** @param className 類名* @return System Class then return true,else return false*/private boolean isSystemClass(String className) {// 假設系統類的類名不為NULL而且不為空if (null == className || className.isEmpty()) {return false;}for (String prefix : SYSTEM_CLASS_PREFIX) {if (className.startsWith(prefix)) {return true;}}return false;} }

2.2、配置MANIFEST.MF

MANIFEST.MF 文件兩種方式生成:手動配置和自動生成,手動配置只需要在 resources 文件下創建 META-INF/MENIFEST.MF 文件即可。除去手動配置外,可以使用maven插件在打包階段自動生成,maven的插件配置如下:

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><configuration><archive><manifestEntries><Premain-Class>io.ct.java.agent.AgentApplication</Premain-Class><Agent-Class>io.ct.java.agent.AgentApplication</Agent-Class><Can-Redefine-Classes>true</Can-Redefine-Classes><Can-Retransform-Classes>true</Can-Retransform-Classes></manifestEntries></archive></configuration></plugin>

生成的jar包格式如下:

其中MANIFEST.MF的文件內容如下(不同的配置生成的文件內容不完全一致):

Manifest-Version: 1.0 Implementation-Title: agent Premain-Class: io.ct.java.agent.AgentApplication Implementation-Version: 0.0.1-SNAPSHOT Built-By: chentong Agent-Class: io.ct.java.agent.AgentApplication Can-Redefine-Classes: true Implementation-Vendor-Id: io.ct.java Can-Retransform-Classes: true Created-By: Apache Maven 3.5.4 Build-Jdk: 1.8.0_171 Implementation-URL: https://projects.spring.io/spring-boot/#/spring-boot-starter-parent/agent

2.3、命令行啟動Java Agent

執行下面的命令,運行已經編譯好的類Hello,可以在同級目錄下生成一個名為Hellodump.class的文件。

java -javaagent:agent-0.0.1-SNAPSHOT.jar Hello

總結

以上是生活随笔為你收集整理的一个简单JavaAgent的实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。