作者:小傅哥 博客:https:// - 匯總系列專題文章
沉淀、分享、成長,讓自己和他人都能有所收獲!
一、前言
截至到本章節(jié)關(guān)于字節(jié)碼框架 Byte-buddy
的大部分常用 API 的使用已經(jīng)通過案例介紹比較全面了,接下來介紹關(guān)于如何去實現(xiàn)一個抽象類以及創(chuàng)建出相應(yīng)注解(包括類的注解和方法的注解)的知識點。而注解的這部分內(nèi)容在一些監(jiān)控或者攔截處理的場景下還是比較常用的,所以在這章節(jié)我們會通過一個例子來創(chuàng)建出含有自定義注解的類和方法。
如果你已經(jīng)閱讀了之前的系列文章,這部分學(xué)習(xí)的內(nèi)容并不會有太多的陌生,主要是關(guān)于委托(MethodDelegation)
方法的使用以及補充自定義注解。
那么 ,接下來我們就使用委托和注解方式來創(chuàng)建這樣的案例進行學(xué)習(xí)。
二、開發(fā)環(huán)境
JDK 1.8.0 byte-buddy 1.10.9 byte-buddy-agent 1.10.9 本章涉及源碼在:itstack-demo-bytecode-2-03
,可以關(guān)注公眾號 :bugstack蟲洞棧
,回復(fù)源碼下載獲取。你會獲得一個下載鏈接列表,打開后里面的第17個「因為我有好多開源代碼」
,記得給個Star
!
三、案例目標(biāo)
在這里我們定義了一個抽象并且含有泛型的接口類,如下;
public abstract class Repository < T> {
public abstract T queryData ( int id) ;
}
那么接下來的案例會使用到委托的方式進行實現(xiàn)抽象類方法并加入自定義注解,也就相當(dāng)于我們使用代碼進行編程實現(xiàn)的效果。
@RpcGatewayClazz ( clazzDesc = "查詢數(shù)據(jù)信息" , alias = "dataApi" , timeOut = 350 L )
public class UserRepository extends Repository < String> {
@RpcGatewayMethod ( methodName = "queryData" , methodDesc = "查詢數(shù)據(jù)" )
public String queryData ( int var1) {
// ...
}
}
這里就是最終效果,我們模擬是一種網(wǎng)關(guān)接口的實現(xiàn)和定義注解暴漏接口信息(如果你是在互聯(lián)網(wǎng)中做開發(fā),類似這樣的需求還是蠻多的,接口統(tǒng)一走網(wǎng)關(guān)服務(wù))。
四、技術(shù)實現(xiàn)
在技術(shù)實現(xiàn)的過程中會逐步的去實現(xiàn)我們需要的功能,將需要的用到知識點信息拆開講解,以達到最終的案例目標(biāo)。
1. 創(chuàng)建自定義注解
模擬網(wǎng)關(guān)類注解
@Retention ( RetentionPolicy. RUNTIME)
@Target ( ElementType. TYPE)
public @interface RpcGatewayClazz {
String clazzDesc ( ) default "" ;
String alias ( ) default "" ;
long timeOut ( ) default 350 ;
}
模擬網(wǎng)關(guān)方法注解
@Retention ( RetentionPolicy. RUNTIME)
@Target ( ElementType. METHOD)
public @interface RpcGatewayMethod {
String methodName ( ) default "" ;
String methodDesc ( ) default "" ;
}
這部分你可以創(chuàng)建任何類型的注解,主要是用于模擬類和方法上分別添加注解并獲取最終屬性值的效果。
2. 創(chuàng)建委托函數(shù)
public class UserRepositoryInterceptor {
public static String intercept ( @Origin Method method, @AllArguments Object[ ] arguments) {
return "小傅哥博客,查詢文章數(shù)據(jù):https:///?id=" + arguments[ 0 ] ;
}
}
最終我們的字節(jié)碼操作會通過委托的方式來實現(xiàn)抽象類的功能。 在委托函數(shù)中的用到注解已經(jīng)在上一章節(jié)中完整的介紹了,可以回顧參考。 @Origin
可以綁定到以下類型的參數(shù):Method 被調(diào)用的原始方法 Constructor 被調(diào)用的原始構(gòu)造器 Class 當(dāng)前動態(tài)創(chuàng)建的類 MethodHandle MethodType String 動態(tài)類的toString()的返回值 int 動態(tài)方法的修飾符.@AllArguments
綁定所有參數(shù)的數(shù)組。
3. 創(chuàng)建方法主體信息
// 生成含有注解的泛型實現(xiàn)字類
DynamicType. Unloaded< ? > dynamicType = new ByteBuddy ( )
. subclass ( TypeDescription. Generic. Builder. parameterizedType ( Repository. class , String. class ) . build ( ) ) // 創(chuàng)建復(fù)雜類型的泛型注解
. name ( Repository. class . getPackage ( ) . getName ( ) . concat ( "." ) . concat ( "UserRepository" ) ) // 添加類信息包括地址
. method ( ElementMatchers. named ( "queryData" ) ) // 匹配處理的方法
. intercept ( MethodDelegation. to ( UserRepositoryInterceptor. class ) ) // 交給委托函數(shù)
. annotateMethod ( AnnotationDescription. Builder. ofType ( RpcGatewayMethod. class ) . define ( "methodName" , "queryData" ) . define ( "methodDesc" , "查詢數(shù)據(jù)" ) . build ( ) )
. annotateType ( AnnotationDescription. Builder. ofType ( RpcGatewayClazz. class ) . define ( "alias" , "dataApi" ) . define ( "clazzDesc" , "查詢數(shù)據(jù)信息" ) . define ( "timeOut" , 350 L) . build ( ) )
. make ( ) ;
這部分基本是Byte-buddy
的模板方法,通過核心API;subclass
、name
、method
、intercept
、annotateMethod
、annotateType
的使用構(gòu)建方法。 首先是定義復(fù)雜類型的自定義注解,設(shè)定為本方法的父類,這部分內(nèi)容也就是抽象類。Repository<T>
,通過 TypeDescription.Generic.Builder.parameterizedType(Repository.class, String.class).build()
來構(gòu)建。 設(shè)定類名稱在我們之前就已經(jīng)使用過,這里多加類的路徑信息。concat
函數(shù)是字符串的連接符,替換 +
號。 method
,設(shè)定匹配處理方法名稱。MethodDelegation.to(UserRepositoryInterceptor.class)
,最終的核心是關(guān)于委托函數(shù)的使用。這里的使用也就可以調(diào)用到我們上面定義的委托函數(shù),等最終我們通過字節(jié)碼生成的 class
類進行查看。annotateMethod
、annotateType
,定義類和方法的注解,通過 define
設(shè)定值(可以多次使用)。
4. 將創(chuàng)建的類寫入目錄
// 輸出類信息到目標(biāo)文件夾下
dynamicType. saveIn ( new File ( ApiTest. class . getResource ( "/" ) . getPath ( ) ) ) ;
這部分內(nèi)容是 Byte-buddy
提供的 API 方法;saveIn
,把字節(jié)碼信息寫成 class
到執(zhí)行的文件夾下。這樣就可以非常方便的驗證通過字節(jié)碼框架創(chuàng)建的方法內(nèi)容。
字節(jié)碼方法內(nèi)容
package org. itstack. demo. bytebuddy;
@RpcGatewayClazz (
clazzDesc = "查詢數(shù)據(jù)信息" ,
alias = "dataApi" ,
timeOut = 350 L
)
public class UserRepository extends Repository < String> {
@RpcGatewayMethod (
methodName = "queryData" ,
methodDesc = "查詢數(shù)據(jù)"
)
public String queryData ( int var1) {
return FindOneInterceptor. intercept ( cachedValue$aGmAjHXh$iha1qv0, new Object [ ] { var1} ) ;
}
public UserRepository ( ) {
}
static {
cachedValue$aGmAjHXh$iha1qv0 = Repository. class . getMethod ( "queryData" , Integer. TYPE) ;
}
}
從上可以看出來我們的自定義類已經(jīng)實現(xiàn)了抽象類,同時也添加了類和方法的注解信息。 而在實現(xiàn)的類中有一步是使用委托函數(shù)進行處理方法的內(nèi)容。
5. 輸出自定義注解信息
// 從目標(biāo)文件夾下加載類信息
Class< Repository< String> > repositoryClass = ( Class< Repository< String> > ) Class. forName ( "org.itstack.demo.bytebuddy.UserRepository" ) ;
// 獲取類注解
RpcGatewayClazz rpcGatewayClazz = repositoryClass. getAnnotation ( RpcGatewayClazz. class ) ;
System. out. println ( "RpcGatewayClazz.clazzDesc:" + rpcGatewayClazz. clazzDesc ( ) ) ;
System. out. println ( "RpcGatewayClazz.alias:" + rpcGatewayClazz. alias ( ) ) ;
System. out. println ( "RpcGatewayClazz.timeOut:" + rpcGatewayClazz. timeOut ( ) ) ;
// 獲取方法注解
RpcGatewayMethod rpcGatewayMethod = repositoryClass. getMethod ( "queryData" , int . class ) . getAnnotation ( RpcGatewayMethod. class ) ;
System. out. println ( "RpcGatewayMethod.methodName:" + rpcGatewayMethod. methodName ( ) ) ;
System. out. println ( "RpcGatewayMethod.methodDesc:" + rpcGatewayMethod. methodDesc ( ) ) ;
在這里我們使用的是 Class.forName
,進行加載類信息。也可以像以前的章節(jié)一樣使用;unloadedType.load(XXX.class.getClassLoader())
的方式進行直接處理字節(jié)碼。 最后是讀取自定義注解的信息內(nèi)容,包括類和方法。
6. 測試驗證運行
// 實例化對象
Repository< String> repository = repositoryClass. newInstance ( ) ;
// 測試輸出
System. out. println ( repository. queryData ( 10001 ) ) ;
通過 Class.forName
的方式就可以直接調(diào)用方法,如果加載字節(jié)碼的方式就需要通過反射進行處理(以往章節(jié)有案例可以對照學(xué)習(xí) )。
測試結(jié)果
RpcGatewayClazz. clazzDesc:查詢數(shù)據(jù)信息
RpcGatewayClazz. alias:dataApi
RpcGatewayClazz. timeOut:350
RpcGatewayMethod. methodName:queryData
RpcGatewayMethod. methodDesc:查詢數(shù)據(jù)
小傅哥博客,查詢文章數(shù)據(jù):https: / / bugstack. cn/ ? id= 10001
Process finished with exit code 0
不出意外你會看到以上的結(jié)果信息,通過我們使用字節(jié)碼創(chuàng)建的方法已經(jīng)可以按照我們的需求進行內(nèi)容輸出。
五、總結(jié)
在本章節(jié)的學(xué)習(xí)中需要注意幾個知識點的使用,包括;委托方法使用
、復(fù)雜類型的泛型創(chuàng)建
、類和方法自定義注解的添加
以及寫入字節(jié)碼信息到文件中
。 截至到目前基本我們已經(jīng)對常用的字節(jié)碼框架自我學(xué)習(xí)和分享的基本完成了,另外一些其他的API的使用可以參考官方文檔;https:// 每一段知識都是只有進行系統(tǒng)化的學(xué)習(xí)才能有完整的收獲,只言片語帶來的碎片化體驗總是不能對一個技術(shù)進行全方面的了解。在技術(shù)的這條路上,多加油!
六、彩蛋
CodeGuide | 程序員編碼指南 Go!
本代碼庫是作者小傅哥多年從事一線互聯(lián)網(wǎng) Java 開發(fā)的學(xué)習(xí)歷程技術(shù)匯總,旨在為大家提供一個清晰詳細的學(xué)習(xí)教程,側(cè)重點更傾向編寫Java核心內(nèi)容。如果本倉庫能為您提供幫助,請給予支持(關(guān)注、點贊、分享)!