自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

自己動手實現(xiàn)Agent統(tǒng)計API接口調(diào)用耗時

開發(fā) 前端
什么是agent?Java Agent也稱為Java探針,它 是一個獨立的 JAR 包,是在JDK1.5中引入的一種技術(shù),允許動態(tài)修改Java字節(jié)碼。這種技術(shù)使得Java應(yīng)用程序的Instrumentation API能夠與虛擬機進行交互。

環(huán)境:Java17

本篇文章介紹java agent技術(shù),然后通過一個示例講解如何使用agent,該示例的功能是通過agent技術(shù)實現(xiàn)api接口調(diào)用耗時情況。

1. 簡介

什么是agent?Java Agent也稱為Java探針,它 是一個獨立的 JAR 包,是在JDK1.5中引入的一種技術(shù),允許動態(tài)修改Java字節(jié)碼。這種技術(shù)使得Java應(yīng)用程序的Instrumentation API能夠與虛擬機進行交互。Java類在編譯之后形成字節(jié)碼,這些字節(jié)碼隨后被JVM執(zhí)行。在JVM執(zhí)行這些字節(jié)碼之前,Java Agent可以獲取這些字節(jié)碼信息,并且通過字節(jié)碼轉(zhuǎn)換器對這些字節(jié)碼進行修改,以實現(xiàn)一些額外的功能。

核心API

Instrumentation

public interface Instrumentation {
  /**
   * 注冊提供的轉(zhuǎn)換器。以后的所有類定義都可以通過轉(zhuǎn)換器看到,但所有已注冊的轉(zhuǎn)換器所依賴的類定義除外。
   * 如果注冊了多個轉(zhuǎn)換器,那么會按添加的順序調(diào)用它們。
   * 如果轉(zhuǎn)換器在執(zhí)行期間拋出異常,則 JVM 仍將按順序調(diào)用其他已注冊的轉(zhuǎn)換器。
   * 可以多次添加同一轉(zhuǎn)換器。在任何外部 JVMTI ClassFileLoadHookAll 事件監(jiān)聽器看到類文件之前,用 addTransformer 注冊的所有轉(zhuǎn)換器始終可以看到類文件。
   */
  void addTransformer(ClassFileTransformer transformer);
  /**
   * 注銷提供的轉(zhuǎn)換器。以后的類定義將不顯示給該轉(zhuǎn)換器。
   * 移除最近添加的轉(zhuǎn)換器的匹配實例。由于類加載的多線程特性,在調(diào)用被移除后,轉(zhuǎn)換器還可能接收調(diào)用。
   * 所以編寫的轉(zhuǎn)換器應(yīng)防止出現(xiàn)這種情況。
   */
  boolean removeTransformer(ClassFileTransformer transformer);
  /**
   * 返回當前 JVM 配置是否支持類的重定義。
   * 重定義已加載類的能力是 JVM 的一個可選功能。
   * 在執(zhí)行單個 JVM 的單實例化過程中,對此方法的多個調(diào)用將始終返回同一應(yīng)答。
   */
  boolean isRedefineClassesSupported();
  /**
   * 使用提供的類文件重新定義提供的類集。
   * 此方法用于在不引用現(xiàn)有類文件字節(jié)的情況下替換類的定義,就像從源代碼重新編譯以修復(fù)并繼續(xù)調(diào)試時可能會做的那樣。
   * 如果要轉(zhuǎn)換現(xiàn)有的類文件字節(jié)(例如字節(jié)碼插入),則應(yīng)使用retransformClassess。
   * 此方法對一個集合進行操作,以便允許同時對多個類進行相互依存的更改(對類a的重新定義可能需要對類B的重新定義)。
   */
  void redefineClasses(ClassDefinition... definitions) throws  ClassNotFoundException, UnmodifiableClassException;
  /**
   * 返回JVM當前加載的所有類的數(shù)組。
   * 返回的數(shù)組包括所有類和接口,包括隱藏類或接口,以及所有類型的數(shù)組類。
   */
  Class[] getAllLoadedClasses();
  /**
   * 返回指定對象所消耗的存儲量的特定于實現(xiàn)的近似值。
   * 該結(jié)果可以包括對象的一些或全部開銷,因此對于實現(xiàn)內(nèi)部的比較是有用的,但對于實現(xiàn)之間的比較則不有用。
   * 在JVM的一次調(diào)用過程中,估計值可能會發(fā)生變化。 
   */
  long getObjectSize(Object objectToSize);
}

ClassFileTransformer/**

* 類文件的轉(zhuǎn)換器。代理使用addTransformer方法注冊該接口的實現(xiàn),以便在加載、重新定義或重新轉(zhuǎn)換類時調(diào)用轉(zhuǎn)換器的轉(zhuǎn)換方法。
 */
public interface ClassFileTransformer {

  /**
   * 轉(zhuǎn)換給定的類文件并返回新的替換類文件
   * loader: 要轉(zhuǎn)換的類的定義加載程序,如果引導(dǎo)加載程序,則可以為null。
   * className: 類的名稱,內(nèi)部形式為完全限定的類和接口名稱,如Java虛擬機規(guī)范中定義的那樣。例如,“java/util/List”。
   * classBeingRedefined: 如果這是由重新定義或重傳觸發(fā)的,則類被重新定義或重新轉(zhuǎn)換;如果這是類裝入,則為null。
   * protectionDomain: 正在定義或重新定義的類的保護域。
   * classfileBuffer: 類文件格式的輸入字節(jié)緩沖區(qū)-不能修改
   */
  default byte[] transform(ClassLoader loader, 
    String className, 
    Class<?> classBeingRedefined, 
    ProtectionDomain protectionDomain, 
    byte[] classfileBuffer) throws IllegalClassFormatException {
    return null;
  }
}

編寫規(guī)范

要編寫一個agent 入口程序有2個核心的方法(二選其一)

 Java 虛擬機 (JVM) 初始化后,premain 方法將被調(diào)用,然后才是真正的應(yīng)用程序 main 方法。premain 方法必須返回才能繼續(xù)啟動。

JVM 首先嘗試在代理類上調(diào)用以下方法:

public static void premain(String agentArgs, Instrumentation instrumentation)

如果代理類未實現(xiàn)此方法,則 JVM 將嘗試調(diào)用:

public static void premain(String agentArgs)

有個上面定義的類之后還需要一個MANIFEST.MF文件

Manifest-Version: 1.0
Premain-Class: com.pack.agent.MonitorAgent
Can-Redefine-Classes: true

Premain-Class:指定了當在 JVM 啟動時指定代理時,此屬性指定代理類。即,包含 premain 方法的類。當在 JVM 啟動時指定代理程序時,此屬性是必需的。如果該屬性不存在,JVM 將中止。

Can-Redefine-Classes:是否能夠重新定義此代理所需的類。

有了上面的基礎(chǔ)知識后接下來我們就通過一個實例來更加清晰的認識Agent。

2. 實戰(zhàn)案例

添加依賴

<!--將通過javassit來修改類信息-->
<dependency>
  <groupId>org.javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.29.2-GA</version>
</dependency>

編寫Transformer類用來轉(zhuǎn)換修改要加載的類

public class MonitorTransformer implements ClassFileTransformer {


  @Override
  public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
      ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    // 在上面的API說明中已經(jīng)說了,這里的className不是. 而是'/'
    className = className.replace("/", ".");
    // 這里只攔截com.pack及子包下的類
    if (className.startsWith("com.pack")) {
      try {
        // 以下的相關(guān)API就是javassit;可自行查看javassit相關(guān)的文章
        CtClass ctClass = ClassPool.getDefault().get(className) ;
        CtMethod[] ctMethods = ctClass.getDeclaredMethods() ;
        for (CtMethod ctMethod : ctMethods) {
          // 獲取執(zhí)行的方法名稱
          String methodName = ctMethod.getName() ;
          // 打印方法執(zhí)行耗時時間
          String executeTime = "\nSystem.out.println(\"" + methodName + " 耗時:\" + (end - start) + " + "\" ms\");\n" ;
          // 添加2個局部變量
          ctMethod.addLocalVariable("start", CtClass.longType) ;
          ctMethod.addLocalVariable("end", CtClass.longType) ;
          // 為上面2個局部變量賦值
          ctMethod.insertBefore("start = System.currentTimeMillis() ;\n") ;
          ctMethod.insertAfter("end = System.currentTimeMillis();\n") ;
          // 將打印時間的語句插入到方法體的最后一行
          ctMethod.insertAfter(executeTime) ;
        }
        // 返回修改后的字節(jié)碼(這里就是重寫字節(jié)碼文件)
        return ctClass.toBytecode();
      } catch (Exception e) {
        e.printStackTrace() ;
      }
    }
    return null;
  }
}

編寫Agent入口

public class MonitorAgent {


  // 這里的premain是我們Agent的入口,首先執(zhí)行的就是該premain,然后才是main
  // agentArgs是agent運行時添加的參數(shù),我們可以在下面看到如何定義參數(shù)
  public static void premain(String agentArgs, Instrumentation instrumentation) {
    // 添加轉(zhuǎn)換器
    instrumentation.addTransformer(new MonitorTransformer());
  }
  // 這里完全沒必要main,只是為了在eclipse中生成jar包方便
  public static void main(String[] args) {
  }
}

編寫MANIFEST.MF文件

Manifest-Version: 1.0
Premain-Class: com.pack.agent.MonitorAgent
Can-Redefine-Classes: true

以上步驟完成后,我們就可以打包了。我是通過Eclipse直接導(dǎo)出的jar,這種方式導(dǎo)出的jar會自動生成MANIFEST.MF文件,所以最后通過壓縮軟件將上面的MANIFEST.MF文件手動添加進去。最后看下生成的jar結(jié)構(gòu)

圖片圖片

編寫SpringBoot程序

這里隨便寫一個API接口即可。

@RestController
@RequestMapping("/demos")
public class DemoController {


  @GetMapping("/index")
  public Object index() throws Exception {
    TimeUnit.SECONDS.sleep(new Random().nextInt(5)) ;
    return "success" ;
  }
}

非常簡單的一個測試接口。我們會通過上面寫的agent來輸出當前接口執(zhí)行時間。

將該測試程序打包成jar,當前目錄。

圖片圖片

MANIFEST.MF不是必須在這里,我這里是為了替換CosAgent.jar中的文件。

接下來是運行,運行需要指定agent jar包。

java -javaagent:CostAgent.jar -jar test.jar

通關(guān)-javaagent:CostAgent.jar指定了agent的jar包,我們可以在后面跟上參數(shù),這樣在premain方法中的第一個參數(shù)就可以接收到參數(shù)信息。

啟動后訪問測試接口/demos/index

index 耗時:0 ms
index 耗時:0 ms
index 耗時:0 ms
index 耗時:1008 ms
index 耗時:2012 ms

我們的接口訪問,成功的輸出了接口調(diào)用耗時時間。

我們可以通過arthas進行查看DemoController接口類

jad com.pack.DemoController

輸出結(jié)果

@GetMapping(value={"/index"})
publicObject index() throws Exception {
   long start = System.currentTimeMillis();
   TimeUnit.SECONDS.sleep(new Random().nextInt(5));
   String string= "success";
   long l = System.currentTimeMillis();
   String string2 = string;
   System.out.println(new StringBuffer().append("index 耗時:").append(l - var1_1).append(" ms").toString());
   return string2;
}

線上的類已經(jīng)通過Agent修改了。

責(zé)任編輯:武曉燕 來源: Spring全家桶實戰(zhàn)案例源碼
相關(guān)推薦

2025-02-22 08:00:00

AgentSpringBootJava

2014-06-20 09:18:54

Dustjs中間件

2024-12-06 09:58:09

2009-10-27 15:07:40

VB.NET支付寶接口

2023-06-15 07:53:07

NeRF深度學(xué)習(xí)

2009-06-01 10:23:31

asp.net mvcasp.net mvc.net mvc框架

2009-10-26 14:25:09

VB.NET控件數(shù)組

2011-08-25 09:30:22

2015-06-02 09:51:40

iOS網(wǎng)絡(luò)請求封裝接口

2020-09-29 12:13:46

SQL引擎底層

2020-05-20 13:53:41

HTTP環(huán)境安裝

2015-09-01 09:49:28

2009-10-28 09:25:18

VB.NET List

2024-10-14 13:07:40

Spring框架Boot

2017-02-14 10:20:43

Java Class解析器

2009-03-16 16:30:18

2018-11-27 09:28:41

API攻擊惡意

2009-10-27 09:01:54

VB.NET內(nèi)存指針

2011-05-06 15:35:58

打印機打印故障

2009-12-03 13:56:05

Suse Linux開xinetd
點贊
收藏

51CTO技術(shù)棧公眾號