探針技術-JavaAgent 和位元組碼增強技術-Byte Buddy

来源:https://www.cnblogs.com/jiagooushi/archive/2022/06/17/16386032.html
-Advertisement-
Play Games

能夠基於Java Agent編寫出普通類的代理 理解Byte Buddy的作用 能夠基於Byte Buddy編寫動態代理 1 Byte Buddy Byte Buddy 是一個代碼生成和操作庫,用於在 Java 應用程式運行時創建和修改 Java 類,而無需編譯器的幫助。除了 Java 類庫附帶的代 ...


  • 能夠基於Java Agent編寫出普通類的代理
  • 理解Byte Buddy的作用
  • 能夠基於Byte Buddy編寫動態代理

1 Byte Buddy

Byte Buddy 是一個代碼生成和操作庫,用於在 Java 應用程式運行時創建和修改 Java 類,而無需編譯器的幫助。除了 Java 類庫附帶的代碼生成實用程式外,Byte Buddy 還允許創建任意類,並且不限於實現用於創建運行時代理的介面。此外,Byte Buddy 提供了一種方便的 API,可以使用 Java 代理或在構建過程中手動更改類。

  • 無需理解位元組碼指令,即可使用簡單的 API 就能很容易操作位元組碼,控制類和方法。
  • 已支持Java 11,庫輕量,僅取決於Java位元組代碼解析器庫ASM的訪問者API,它本身不需要任何其他依賴項。
  • 比起JDK動態代理、cglib、Javassist,Byte Buddy在性能上具有一定的優勢。

官網:https://bytebuddy.net

1.1 Byte Buddy應用場景

Java 是一種強類型的編程語言,即要求所有變數和對象都有一個確定的類型,如果在賦值操作中出現類型不相容的情況,就會拋出異常。強類型檢查在大多數情況下是可行的,然而在某些特殊場景下,強類型檢查則成了巨大的障礙。

我們在做一些通用工具封裝的時候,類型檢查就成了很大障礙。比如我們編寫一個通用的Dao實現數據操作,我們根本不知道用戶要調用的方法會傳幾個參數、每個參數是什麼類型、需求變更又會出現什麼類型,幾乎沒法在方法中引用用戶方法中定義的任何類型。我們絕大多數通用工具封裝都採用了反射機制,通過反射可以知道用戶調用的方法或欄位,但是Java反射有很多缺陷:

1:反射性能很差
2:反射能繞開類型安全檢查,不安全,比如許可權暴力破解

java編程語言代碼生成庫不止 Byte Buddy 一個,以下代碼生成庫在 Java 中也很流行:

  • Java Proxy

Java Proxy 是 JDK 自帶的一個代理工具,它允許為實現了一系列介面的類生成代理類。Java Proxy 要求目標類必須實現介面是一個非常大限制,例如,在某些場景中,目標類沒有實現任何介面且無法修改目標類的代碼實現,Java Proxy 就無法對其進行擴展和增強了。

  • CGLIB

CGLIB 誕生於 Java 初期,但不幸的是沒有跟上 Java 平臺的發展。雖然 CGLIB 本身是一個相當強大的庫,但也變得越來越複雜。鑒於此,導致許多用戶放棄了 CGLIB 。

  • Javassist

Javassist 的使用對 Java 開發者來說是非常友好的,它使用Java 源代碼字元串和 Javassist 提供的一些簡單 API ,共同拼湊出用戶想要的 Java 類,Javassist 自帶一個編譯器,拼湊好的 Java 類在程式運行時會被編譯成為位元組碼並載入到 JVM 中。Javassist 庫簡單易用,而且使用 Java 語法構建類與平時寫 Java 代碼類似,但是 Javassist 編譯器在性能上比不了 Javac 編譯器,而且在動態組合字元串以實現比較複雜的邏輯時容易出錯。

  • Byte Buddy

Byte Buddy 提供了一種非常靈活且強大的領域特定語言,通過編寫簡單的 Java 代碼即可創建自定義的運行時類。與此同時,Byte Buddy 還具有非常開放的定製性,能夠應付不同複雜度的需求。

上面所有代碼生成技術中,我們推薦使用Byte Buddy,因為Byte Buddy代碼生成可的性能最高,Byte Buddy 的主要側重點在於生成更快速的代碼,如下圖:

file

1.2 Byte Buddy學習

我們接下來詳細講解一下Byte Buddy Api,對重要的方法和類進行深度剖析。

1.2.1 ByteBuddy語法

任何一個由 Byte Buddy 創建/增強的類型都是通過 ByteBuddy 類的實例來完成的,我們先來學習一下ByteBuddy類,如下代碼:

DynamicType.Unloaded<?> dynamicType = new ByteBuddy()
        // 生成 Object的子類
        .subclass(Object.class)
        // 生成類的名稱為"com.itheima.Type"
        .name("com.itheima.Type")
        .make();

Byte Buddy 動態增強代碼總共有三種方式:

subclass:對應 ByteBuddy.subclass() 方法。這種方式比較好理解,就是為目標類(即被增強的類)生成一個子類,在子類方法中插入動態代碼。

rebasing:對應 ByteBuddy.rebasing() 方法。當使用 rebasing 方式增強一個類時,Byte Buddy 保存目標類中所有方法的實現,也就是說,當 Byte Buddy 遇到衝突的欄位或方法時,會將原來的欄位或方法實現複製到具有相容簽名的重新命名的私有方法中,而不會拋棄這些欄位和方法實現。從而達到不丟失實現的目的。這些重命名的方法可以繼續通過重命名後的名稱進行調用。

redefinition:對應 ByteBuddy.redefine() 方法。當重定義一個類時,Byte Buddy 可以對一個已有的類添加屬性和方法,或者刪除已經存在的方法實現。如果使用其他的方法實現替換已經存在的方法實現,則原來存在的方法實現就會消失。

通過上述三種方式完成類的增強之後,我們得到的是 DynamicType.Unloaded 對象,表示的是一個未載入的類型,我們可以使用 ClassLoadingStrategy 載入此類型。Byte Buddy 提供了幾種類載入策略,這些載入策略定義在 ClassLoadingStrategy.Default 中,其中:

  • WRAPPER 策略:創建一個新的 ClassLoader 來載入動態生成的類型。
  • CHILD_FIRST 策略:創建一個子類優先載入的 ClassLoader,即打破了雙親委派模型。
  • INJECTION 策略:使用反射將動態生成的類型直接註入到當前 ClassLoader 中。

實現如下:

Class<?> dynamicClazz = new ByteBuddy()
        // 生成 Object的子類
        .subclass(Object.class)
        // 生成類的名稱為"com.itheima.Type"
        .name("com.itheima.Type")
        .make()
        .load(Demo.class.getClassLoader(),
                //使用WRAPPER 策略載入生成的動態類型
                ClassLoadingStrategy.Default.WRAPPER)
        .getLoaded();

前面動態生成的 com.itheima.Type 類型只是簡單的繼承了 Object 類,在實際應用中動態生成新類型的一般目的就是為了增強原始的方法,下麵通過一個示例展示 Byte Buddy 如何增強 toString() 方法:

// 創建ByteBuddy對象
String str = new ByteBuddy()
        // subclass增強方式
        .subclass(Object.class)
        // 新類型的類名
        .name("com.itheima.Type") 
        // 攔截其中的toString()方法
        .method(ElementMatchers.named("toString"))
        // 讓toString()方法返回固定值
        .intercept(FixedValue.value("Hello World!"))
        .make()
        // 載入新類型,預設WRAPPER策略
        .load(ByteBuddy.class.getClassLoader())
        .getLoaded()
        // 通過 Java反射創建 com.xxx.Type實例
        .newInstance()
        // 調用 toString()方法
        .toString(); 

首先需要關註這裡的 method() 方法,method() 方法可以通過傳入的 ElementMatchers 參數匹配多個需要修改的方法,這裡的ElementMatchers.named("toString") 即為按照方法名匹配 toString() 方法。如果同時存在多個重載方法,則可以使用 ElementMatchers 其他 API 描述方法的簽名,如下所示:

// 指定方法名稱
ElementMatchers.named("toString")
    // 指定方法的返回值
    .and(ElementMatchers.returns(String.class))
    // 指定方法參數
    .and(ElementMatchers.takesArguments(0));

接下來需要關註的是 intercept() 方法,通過 method() 方法攔截到的所有方法會由 Intercept() 方法指定的 Implementation 對象決定如何增強。這裡的 FixValue.value() 會將方法的實現修改為固定值,上例中就是固定返回 “Hello World!” 字元串。

Byte Buddy 中可以設置多個 method()Intercept() 方法進行攔截和修改,Byte Buddy 會按照棧的順序來進行攔截。

1.2.2 ByteBuddy 案例

創建一個項目agent-demo,添加如下坐標

<dependencies>
    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy</artifactId>
        <version>1.9.2</version>
    </dependency>
    <dependency>
        <groupId>net.bytebuddy</groupId>
        <artifactId>byte-buddy-agent</artifactId>
        <version>1.9.2</version>
    </dependency>
</dependencies>

我們先創建一個普通類,再為該類創建代理類,創建代理對方法進行攔截做處理。

1)普通類

創建com.itheima.service.UserService

package com.itheima.service;

public class UserService {

    //方法1
    public String username(){
        System.out.println("username().....");
        return "張三";
    }

    //方法2
    public String address(String username){
        System.out.println("address(String username).....");
        return username+"來自 【湖北省-仙居-恩施土家族苗族自治州】";
    }

    //方法3
    public String address(String username,String city){
        System.out.println("address(String username,String city).....");
        return username+"來自 【湖北省"+city+"】";
    }
}

2)代理測試com.itheima.service.UserServiceTest

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        Class<? extends UserService> aClass = new ByteBuddy()
                // 創建一個UserService 的子類
                .subclass(UserService.class)
                //指定類的名稱
                .name("com.itheima.service.UserServiceImpl")
                // 指定要攔截的方法
                //.method(ElementMatchers.isDeclaredBy(UserService.class))
                .method(ElementMatchers.named("address").and(ElementMatchers.returns(String.class).and(ElementMatchers.takesArguments(1))))
                // 為方法添加攔截器 如果攔截器方法是靜態的 這裡可以傳 LogInterceptor.class
                .intercept(MethodDelegation.to(new LogInterceptor()))
                // 動態創建對象,但還未載入
                .make()
                // 設置類載入器 並指定載入策略(預設WRAPPER)
                .load(ByteBuddy.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                // 開始載入得到 Class
                .getLoaded();
        UserService userService = aClass.newInstance();

        System.out.println(userService.username());
        System.out.println(userService.address("唐僧老師"));
        System.out.println(userService.address("唐僧老師","仙居恩施"));
    }

3)創建攔截器,編寫攔截器方法:com.itheima.service.LogInterceptor

package com.itheima.service;

import net.bytebuddy.implementation.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.concurrent.Callable;

public class LogInterceptor {

    @RuntimeType //將返回值轉換成具體的方法返回值類型,加了這個註解 intercept 方法才會被執行
    public  Object intercept(
            // 被攔截的目標對象 (動態生成的目標對象)
            @This  Object target,
            // 正在執行的方法Method 對象(目標對象父類的Method)
            @Origin Method method,
            // 正在執行的方法的全部參數
            @AllArguments Object[] argumengts,
            // 目標對象的一個代理
            @Super  Object delegate,
            // 方法的調用者對象 對原始方法的調用依靠它
            @SuperCall Callable<?> callable) throws Exception {
        //目標方法執行前執行日誌記錄
        System.out.println("準備執行Method="+method.getName());
        // 調用目標方法
        Object result = callable.call();
        //目標方法執行後執行日誌記錄
        System.out.println("方法執行完成Method="+method.getName());
        return result;
    }

}

在程式中我們 用到ByteBuddyMethodDelegation對象,它可以將攔截的目標方法委托給其他對象處理,這裡有幾個註解我們先進行說明:

  • @RuntimeType:不進行嚴格的參數類型檢測,在參數匹配失敗時,嘗試使用類型轉換方式(runtime type casting)進行類型轉換,匹配相應方法。
  • @This:註入被攔截的目標對象(動態生成的目標對象)。
  • @Origin:註入正在執行的方法Method 對象(目標對象父類的Method)。如果攔截的是欄位的話,該註解應該標註到 Field 類型參數。
  • @AllArguments:註入正在執行的方法的全部參數。
  • @Super:註入目標對象的一個代理
  • @SuperCall:這個註解比較特殊,我們要在 intercept() 方法中調用 被代理/增強 的方法的話,需要通過這種方式註入,與 Spring AOP 中的 ProceedingJoinPoint.proceed() 方法有點類似,需要註意的是,這裡不能修改調用參數,從上面的示例的調用也能看出來,參數不用單獨傳遞,都包含在其中了。另外,@SuperCall 註解還可以修飾 Runnable 類型的參數,只不過目標方法的返回值就拿不到了。

運行測試結果:

準備執行Method=username
username().....
方法執行完成Method=username
張三
準備執行Method=address
address(String username).....
方法執行完成Method=address
唐僧老師來自 【湖北省-仙居-恩施土家族苗族自治州】
準備執行Method=address
address(String username,String city).....
方法執行完成Method=address
唐僧老師來自 【湖北省仙居恩施】

2 探針技術-javaAgent

使用Skywalking的時候,並沒有修改程式中任何一行 Java 代碼,這裡便使用到了 Java Agent 技術,我們接下來展開對Java Agent 技術的學習。

2.1 javaAgent概述

Java Agent這個技術對大多數人來說都比較陌生,但是大家都都多多少少接觸過一些,實際上我們平時用過的很多工具都是基於java Agent來實現的,例如:熱部署工具JRebel,springboot的熱部署插件,各種線上診斷工具(btrace, greys),阿裡開源的arthas等等。

其實java Agent在JDK1.5以後,我們可以使用agent技術構建一個獨立於應用程式的代理程式(即Agent),用來協助監測、運行甚至替換其他JVM上的程式。使用它可以實現虛擬機級別的AOP功能,並且這種方式一個典型的優勢就是無代碼侵入。

Agent分為兩種:

1、在主程式之前運行的Agent,

2、在主程式之後運行的Agent(前者的升級版,1.6以後提供)。

2.2 javaAgent入門

2.2.1 premain

premain:主程式之前運行的Agent

在實際使用過程中,javaagent是java命令的一個參數。通過java 命令啟動我們的應用程式的時候,可通過參數 -javaagent 指定一個 jar 包(也就是我們的代理agent),能夠實現在我們應用程式的主程式運行之前來執行我們指定jar包中的特定方法,在該方法中我們能夠實現動態增強Class等相關功能,並且該 jar包有2個要求:

  1. 這個 jar 包的 META-INF/MANIFEST.MF 文件必須指定 Premain-Class 項,該選項指定的是一個類的全路徑

  2. Premain-Class 指定的那個類必須實現 premain() 方法。

    META-INF/MANIFEST.MF

    Manifest-Version: 1.0
    Can-Redefine-Classes: true
    Can-Retransform-Classes: true
    Premain-Class: com.itheima.PreMainAgent
    
    

    註意:最後需要多一行空行

    Can-Redefine-Classes :true表示能重定義此代理所需的類,預設值為 false(可選)

    Can-Retransform-Classes :true 表示能重轉換此代理所需的類,預設值為 false (可選)

    Premain-Class :包含 premain 方法的類(類的全路徑名)

從字面上理解,Premain-Class 就是運行在 main 函數之前的的類。當Java 虛擬機啟動時,在執行 main 函數之前,JVM 會先運行-javaagent所指定 jar 包內 Premain-Class 這個類的 premain 方法 。

我們可以通過在命令行輸入java看到相應的參數,其中就有和java agent相關的

file

在上面-javaagent參數中提到了參閱java.lang.instrument,這是在rt.jar 中定義的一個包,該路徑下有兩個重要的類:

file

該包提供了一些工具幫助開發人員在 Java 程式運行時,動態修改系統中的 Class 類型。其中,使用該軟體包的一個關鍵組件就是 Javaagent,如果從本質上來講,Java Agent 是一個遵循一組嚴格約定的常規 Java 類。 上面說到 javaagent命令要求指定的類中必須要有premain()方法,並且對premain方法的簽名也有要求,簽名必須滿足以下兩種格式:

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

JVM 會優先載入 帶 Instrumentation 簽名的方法,載入成功忽略第二種,如果第一種沒有,則載入第二種方法

demo:

1、在agent-demo中添加如下坐標

<build>
    <plugins>
        <plugin>
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <appendAssemblyId>false</appendAssemblyId>
                <descriptorRefs>
                    <descriptorRef>jar-with-dependencies</descriptorRef>
                </descriptorRefs>
                <archive> <!--自動添加META-INF/MANIFEST.MF -->
                    <manifest>
                        <!-- 添加 mplementation-*和Specification-*配置項-->
                        <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                        <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
                    </manifest>
                    <manifestEntries>
                        <!--指定premain方法所在的類-->
                        <Premain-Class>com.itheima.agent.PreMainAgent</Premain-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
            <executions>
                <execution>
                    <id>make-assembly</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

2、編寫一個agent程式:com.itheima.agent.PreMainAgent,完成premain方法的簽名,先做一個簡單的輸出

package com.itheima.agent;

import java.lang.instrument.Instrumentation;

public class PreMainAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("我的agent程式跑起來啦!");
        System.out.println("收到的agent參數是:"+agentArgs);
    }
}

下麵先來簡單介紹一下 Instrumentation 中的核心 API 方法:

  • addTransformer()/removeTransformer() 方法:註冊/註銷一個 ClassFileTransformer 類的實例,該 Transformer 會在類載入的時候被調用,可用於修改類定義(修改類的位元組碼)。
  • redefineClasses() 方法:該方法針對的是已經載入的類,它會對傳入的類進行重新定義。
  • getAllLoadedClasses()方法:返回當前 JVM 已載入的所有類。
  • getInitiatedClasses() 方法:返回當前 JVM 已經初始化的類。
  • getObjectSize()方法:獲取參數指定的對象的大小。

3、對agent-demo項目進行打包,得到 agent-demo-1.0-SNAPSHOT.jar

4、創建agent-test項目,編寫一個類:com.itheima.Application

package com.itheima;

public class Application {

    public static void main(String[] args) {
        System.out.println("main 函數 運行了 ");
    }
}

5、啟動運行,添加-javaagent參數

-javaagent:/xxx.jar=option1=value1,option2=value2

file

運行結果為:

我的agent程式跑起來啦!
收到的agent參數是:k1=v1,k2=v2
main 函數 運行了 

總結:

這種agent JVM 會先執行 premain 方法,大部分類載入都會通過該方法,註意:是大部分,不是所有。當然,遺漏的主要是系統類,因為很多系統類先於 agent 執行,而用戶類的載入肯定是會被攔截的。也就是說,這個方法是在 main 方法啟動前攔截大部分類的載入活動,既然可以攔截類的載入,那麼就可以去做重寫類這樣的操作,結合第三方的位元組碼編譯工具,比如ASM,bytebuddy,javassist,cglib等等來改寫實現類。

2.2.2 agentmain(自學)

agentmain:主程式之後運行的Agent

上面介紹的是在 JDK 1.5中提供的,開發者只能在main載入之前添加手腳,在 Java SE 6 中提供了一個新的代理操作方法:agentmain,可以在 main 函數開始運行之後再運行。

premain函數一樣, 開發者可以編寫一個含有agentmain函數的 Java 類,具備以下之一的方法即可

public static void agentmain (String agentArgs, Instrumentation inst)

public static void agentmain (String agentArgs)

同樣需要在MANIFEST.MF文件裡面設置“Agent-Class”來指定包含 agentmain 函數的類的全路徑。

1:在agentdemo中創建一個新的類:com.itheima.agent.AgentClass,並編寫方法agenmain

/**
 * Created by 傳智播客*黑馬程式員.
 */
public class AgentClass {

    public static void agentmain (String agentArgs, Instrumentation inst){
        System.out.println("agentmain runing");
    }
}

2:在pom.xml中添加配置如下

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
        <appendAssemblyId>false</appendAssemblyId>
        <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
        <archive> <!--自動添加META-INF/MANIFEST.MF -->
            <manifest>
                <!-- 添加 mplementation-*和Specification-*配置項-->
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
            </manifest>
            <manifestEntries>
                <!--指定premain方法所在的類-->
                <Premain-Class>com.itheima.agent.PreMainAgent</Premain-Class>
                <!--添加這個即可-->
                <Agent-Class>com.itheima.agent.AgentClass</Agent-Class>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
            </manifestEntries>
        </archive>
    </configuration>
    <executions>
        <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
                <goal>single</goal>
            </goals>
        </execution>
    </executions>
</plugin>

3:對agent-demo重新打包

4:找到agent-test中的Application,修改如下:

public class Application {

    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        System.out.println("main 函數 運行了 ");

        //獲取當前系統中所有 運行中的 虛擬機
        List<VirtualMachineDescriptor> list = VirtualMachine.list();
        for (VirtualMachineDescriptor vm : list) {
            if (vm.displayName().endsWith("com.itheima.Application")) {
                VirtualMachine virtualMachine = VirtualMachine.attach(vm.id());
                virtualMachine.loadAgent("D:/agentdemo.jar");
                virtualMachine.detach();
            }
        }
    }
}

list()方法會去尋找當前系統中所有運行著的JVM進程,你可以列印vmd.displayName()看到當前系統都有哪些JVM進程在運行。因為main函數執行起來的時候進程名為當前類名,所以通過這種方式可以去找到當前的進程id。

註意:在mac上安裝了的jdk是能直接找到 VirtualMachine 類的,但是在windows中安裝的jdk無法找到,如果你遇到這種情況,請手動將你jdk安裝目錄下:lib目錄中的tools.jar添加進當前工程的Libraries中。

之所以要這樣寫是因為:agent要在主程式運行後載入,我們不可能在主程式中編寫載入的代碼,只能另寫程式,那麼另寫程式如何與主程式進行通信?這裡用到的機制就是attach機制,它可以將JVM A連接至JVM B,併發送指令給JVM B執行。

總結:

以上就是Java Agent的倆個簡單小慄子了,Java Agent十分強大,它能做到的不僅僅是列印幾個監控數值而已,還包括使用Transformer等高級功能進行類替換,方法修改等,要使用Instrumentation的相關API則需要對位元組碼等技術有較深的認識。

2.3 agent 案例

需求:通過 java agent 技術實現一個統計方法耗時的案例

1、在agent-test項目中添加方法:com.itheima.driver.DriverService

package com.itheima.driver;

import java.util.concurrent.TimeUnit;

public class DriverService {


    public void didi() {
        System.out.println("di di di ------------");
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void dada() {
        System.out.println("da da da ------------");
        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

併在com.itheima.Application進行方法的調用

package com.itheima;

import com.itheima.service.DriverService;

public class Application {

    public static void main(String[] args) {
        System.out.println("main 函數 運行了 ");
        DriverService driverService = new DriverService();
        driverService.didi();
        driverService.dada();
    }
}

2、在agent-demo中改造com.itheima.agent.PreMainAgent

public class PreMainAgent {

    /***
     * 執行方法攔截
     * @param agentArgs:-javaagent 命令攜帶的參數。在前面介紹 SkyWalking Agent 接入時提到
     *                 agent.service_name 這個配置項的預設值有三種覆蓋方式,
     *                 其中,使用探針配置進行覆蓋,探針配置的值就是通過該參數傳入的。
     * @param inst:java.lang.instrumen.Instrumentation 是 Instrumention 包中定義的一個介面,它提供了操作類定義的相關方法。
     */
    public static void premain(String agentArgs, Instrumentation inst){
        //動態構建操作,根據transformer規則執行攔截操作
        AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
            @Override
            public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder,
                                                    // 匹配上的具體的類型描述
                                                    TypeDescription typeDescription,
                                                    ClassLoader classLoader,
                                                    JavaModule javaModule) {
                //構建攔截規則
                return builder
                        //method()指定哪些方法需要被攔截,ElementMatchers.any()表示攔截所有方法
                        .method(ElementMatchers.<MethodDescription>any())
                        //intercept()指定攔截上述方法的攔截器
                        .intercept(MethodDelegation.to(TimeInterceptor.class));
            }
        };

        //採用Byte Buddy的AgentBuilder結合Java Agent處理程式
        new AgentBuilder
                //採用ByteBuddy作為預設的Agent實例
                .Default()
                //攔截匹配方式:類以com.itheima.driver開始(其實就是com.itheima.driver包下的所有類)
                .type(ElementMatchers.nameStartsWith("com.itheima.driver"))
                //攔截到的類由transformer處理
                .transform(transformer)
                //安裝到 Instrumentation
                .installOn(inst);
    }
}

agent-demo項目中,創建com.itheima.service.TimeInterceptor實現統計攔截,代碼如下:

public class TimeInterceptor {

    /***
     * 攔截方法
     * @param method:攔截的方法
     * @param callable:調用對象的代理對象
     * @return
     * @throws Exception
     */
    @RuntimeType // 聲明為static
    public static Object intercept(@Origin Method method,
                                   @SuperCall Callable<?> callable) throws Exception {
        //時間統計開始
        long start = System.currentTimeMillis();
        // 執行原函數
        Object result = callable.call();
        //執行時間統計
        System.out.println(method.getName() + ":執行耗時" + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

3、重新打包agent-demo,然後再次測試運行agent-test中的主類 Application

試效果如下:

 premain 執行了
main 函數 運行了 
di di di ------------
didi:執行耗時5009ms
da da da ------------
dada:執行耗時6002ms

本文由傳智教育博學谷 - 狂野架構師教研團隊發佈
如果本文對您有幫助,歡迎關註和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力
轉載請註明出處!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄 一.簡介 二.效果演示 三.源碼下載 四.猜你喜歡 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 基礎 零基礎 OpenGL (ES) 學習路線推薦 : OpenGL (ES) 學習目錄 >> OpenGL ES 轉場 零基礎 O ...
  • 分類方式 按參數分: 有參構造(預設構造) & 無參構造 按類型分: 普通構造 & 拷貝構造 調用方式 括弧法 顯示法 隱式轉換法 PS:下方所有文本均以此代碼為基礎 1 class Person { 2 public: 3 //無參構造函數 4 Person() { 5 std::cout << ...
  • 引言:今天工作遇到了一個需要按行讀取txt文件數據的需求,查詢了一下getline()函數,發現這竟然是一個C++的標準庫函數,而且設計的很好,特地做一下記錄。getline本質是一個定界流輸入截取函數,預設是換行符‘/n’ 個人技術博客(文章整理+源碼): https://zobolblog.gi ...
  • 大佬的理解->《IO流和File》 1、File類 File類是IO包中唯一代表磁碟文件本身的對象,File類定義了一些與平臺無關的方法來操作文件。通過調用File類提供的各種方法,能夠完成創建、刪除文件、重命名文件、判斷文件的讀寫許可權許可權是否存在、設置和查詢文件的最近修改時間等操作。 ​ File ...
  • 前言 大部分情況下,地理繪圖可使用 Arcgis 等工具實現。但正版的 Arcgis 並非所有人可以承受。本文基於 Python 的 cartopy 和 matplotlib 等庫,為地理空間繪圖的代碼實現提供參考。 所有所需庫如下: gma、cartopy、matplotlib、numpy 更多內 ...
  • 1 問題的提出 對於給定的數據集 \(D = \{(x_1,y_1),(x_2,y_2),\cdots,(x_m,y_m)\}\),線性回歸 (linear regression) 試圖學得一個線性模型以儘可能準確地預測是指輸出標記. 2 原理 設給定的數據集 \(D = \{(x_i,y_i)\} ...
  • 基礎數據準備 基礎數據是通過爬蟲獲取到。 以下是從第一期03年雙色球開獎號到今天的所有數據整理,截止目前一共2549期,balls.txt 文件內容如下 Python 代碼實現 分析數據特征和數據處理方式選擇 python學習交流Q群:906715085### #導入Counter from col ...
  • 前幾天看了一篇文章,自己動手試了下,發現有些不一樣結論,作博客記錄下,本文主要研究兩個問題: 包裝流的close方法是否會自動關閉被包裝的流? 關閉流方法是否有順序? 包裝流的close方法是否會自動關閉被包裝的流? 平時我們使用輸入流和輸出流一般都會使用buffer包裝一下,直接看下麵代碼(這個代 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...