字節碼編程,Byte-buddy篇三《使用委托實現抽象類方法并注入自定義注解信息》
沉淀、分享、成長,讓自己和他人都能有所收獲!
一、前言
截至到本章節關于字節碼框架 Byte-buddy 的大部分常用 API 的使用已經通過案例介紹比較全面了,接下來介紹關于如何去實現一個抽象類以及創建出相應注解(包括類的注解和方法的注解)的知識點。而注解的這部分內容在一些監控或者攔截處理的場景下還是比較常用的,所以在這章節我們會通過一個例子來創建出含有自定義注解的類和方法。
如果你已經閱讀了之前的系列文章,這部分學習的內容并不會有太多的陌生,主要是關于委托(MethodDelegation)方法的使用以及補充自定義注解。
那么,接下來我們就使用委托和注解方式來創建這樣的案例進行學習。
二、開發環境
JDK 1.8.0
byte-buddy 1.10.9
byte-buddy-agent 1.10.9
本章涉及源碼在:itstack-demo-bytecode-2-03,可以關注公眾號:bugstack蟲洞棧,回復源碼下載獲取。你會獲得一個下載鏈接列表,打開后里面的第17個「因為我有好多開源代碼」,記得給個Star!
三、案例目標
在這里我們定義了一個抽象并且含有泛型的接口類,如下;
public abstract class Repository
1
2
3
4
5
那么接下來的案例會使用到委托的方式進行實現抽象類方法并加入自定義注解,也就相當于我們使用代碼進行編程實現的效果。
@RpcGatewayClazz( clazzDesc = "查詢數據信息", alias = "dataApi", timeOut = 350L ) public class UserRepository extends Repository
1
2
3
4
5
6
7
8
9
這里就是最終效果,我們模擬是一種網關接口的實現和定義注解暴漏接口信息(如果你是在互聯網中做開發,類似這樣的需求還是蠻多的,接口統一走網關服務)。
四、技術實現
在技術實現的過程中會逐步的去實現我們需要的功能,將需要的用到知識點信息拆開講解,以達到最終的案例目標。
1. 創建自定義注解
模擬網關類注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface RpcGatewayClazz { String clazzDesc() default ""; String alias() default ""; long timeOut() default 350; }
1
2
3
4
5
6
7
8
9
模擬網關方法注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RpcGatewayMethod { String methodName() default ""; String methodDesc() default ""; }
1
2
3
4
5
6
7
8
這部分你可以創建任何類型的注解,主要是用于模擬類和方法上分別添加注解并獲取最終屬性值的效果。
2. 創建委托函數
public class UserRepositoryInterceptor { public static String intercept(@Origin Method method, @AllArguments Object[] arguments) { return "小傅哥博客,查詢文章數據:https://bugstack.cn/?id=" + arguments[0]; } }
1
2
3
4
5
6
7
最終我們的字節碼操作會通過委托的方式來實現抽象類的功能。
在委托函數中的用到注解已經在上一章節中完整的介紹了,可以回顧參考。
@Origin 可以綁定到以下類型的參數:Method 被調用的原始方法 Constructor 被調用的原始構造器 Class 當前動態創建的類 MethodHandle MethodType String 動態類的toString()的返回值 int 動態方法的修飾符.
@AllArguments 綁定所有參數的數組。
3. 創建方法主體信息
// 生成含有注解的泛型實現字類 DynamicType.Unloaded> dynamicType = new ByteBuddy() .subclass(TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build()) // 創建復雜類型的泛型注解 .name(Repository.class.getPackage().getName().concat(".").concat("UserRepository")) // 添加類信息包括地址 .method(ElementMatchers.named("queryData")) // 匹配處理的方法 .intercept(MethodDelegation.to(UserRepositoryInterceptor.class)) // 交給委托函數 .annotateMethod(AnnotationDescription.Builder.ofType(RpcGatewayMethod.class).define("methodName", "queryData").define("methodDesc", "查詢數據").build()) .annotateType(AnnotationDescription.Builder.ofType(RpcGatewayClazz.class).define("alias", "dataApi").define("clazzDesc", "查詢數據信息").define("timeOut", 350L).build()) .make();
1
2
3
4
5
6
7
8
9
這部分基本是Byte-buddy的模板方法,通過核心API;subclass、name、method、intercept、annotateMethod、annotateType 的使用構建方法。
首先是定義復雜類型的自定義注解,設定為本方法的父類,這部分內容也就是抽象類。Repository
設定類名稱在我們之前就已經使用過,這里多加類的路徑信息。concat 函數是字符串的連接符,替換 + 號。
method,設定匹配處理方法名稱。
MethodDelegation.to(UserRepositoryInterceptor.class),最終的核心是關于委托函數的使用。這里的使用也就可以調用到我們上面定義的委托函數,等最終我們通過字節碼生成的 class 類進行查看。
annotateMethod、annotateType,定義類和方法的注解,通過 define 設定值(可以多次使用)。
4. 將創建的類寫入目錄
// 輸出類信息到目標文件夾下 dynamicType.saveIn(new File(ApiTest.class.getResource("/").getPath()));
1
2
這部分內容是 Byte-buddy 提供的 API 方法;saveIn,把字節碼信息寫成 class 到執行的文件夾下。這樣就可以非常方便的驗證通過字節碼框架創建的方法內容。
字節碼方法內容
package org.itstack.demo.bytebuddy; @RpcGatewayClazz( clazzDesc = "查詢數據信息", alias = "dataApi", timeOut = 350L ) public class UserRepository extends Repository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
從上可以看出來我們的自定義類已經實現了抽象類,同時也添加了類和方法的注解信息。
而在實現的類中有一步是使用委托函數進行處理方法的內容。
5. 輸出自定義注解信息
// 從目標文件夾下加載類信息 Class
1
2
3
4
5
6
7
8
9
10
11
12
13
在這里我們使用的是 Class.forName,進行加載類信息。也可以像以前的章節一樣使用;unloadedType.load(XXX.class.getClassLoader()) 的方式進行直接處理字節碼。
最后是讀取自定義注解的信息內容,包括類和方法。
6. 測試驗證運行
// 實例化對象 Repository
1
2
3
4
通過 Class.forName 的方式就可以直接調用方法,如果加載字節碼的方式就需要通過反射進行處理(以往章節有案例可以對照學習)。
測試結果
RpcGatewayClazz.clazzDesc:查詢數據信息 RpcGatewayClazz.alias:dataApi RpcGatewayClazz.timeOut:350 RpcGatewayMethod.methodName:queryData RpcGatewayMethod.methodDesc:查詢數據 小傅哥博客,查詢文章數據:https://bugstack.cn/?id=10001 Process finished with exit code 0
1
2
3
4
5
6
7
8
不出意外你會看到以上的結果信息,通過我們使用字節碼創建的方法已經可以按照我們的需求進行內容輸出。
五、總結
在本章節的學習中需要注意幾個知識點的使用,包括;委托方法使用、復雜類型的泛型創建、類和方法自定義注解的添加以及寫入字節碼信息到文件中。
截至到目前基本我們已經對常用的字節碼框架自我學習和分享的基本完成了,另外一些其他的API的使用可以參考官方文檔;https://bytebuddy.net
每一段知識都是只有進行系統化的學習才能有完整的收獲,只言片語帶來的碎片化體驗總是不能對一個技術進行全方面的了解。在技術的這條路上,多加油!
六、彩蛋
API
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。