能夠基於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在性能上具有一定的優勢。
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 的主要側重點在於生成更快速的代碼,如下圖:
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;
}
}
在程式中我們 用到ByteBuddy
的MethodDelegation
對象,它可以將攔截的目標方法委托給其他對象處理,這裡有幾個註解我們先進行說明:
- @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個要求:
-
這個 jar 包的 META-INF/MANIFEST.MF 文件必須指定 Premain-Class 項,該選項指定的是一個類的全路徑
-
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相關的
在上面-javaagent
參數中提到了參閱java.lang.instrument
,這是在rt.jar
中定義的一個包,該路徑下有兩個重要的類:
該包提供了一些工具幫助開發人員在 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
運行結果為:
我的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
本文由傳智教育博學谷 - 狂野架構師教研團隊發佈
如果本文對您有幫助,歡迎關註和點贊;如果您有任何建議也可留言評論或私信,您的支持是我堅持創作的動力
轉載請註明出處!