日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

字節(jié)碼編程,Byte-buddy篇三《使用委托實現(xiàn)抽象類方法并注入自定義注解信息》

 小傅哥 2021-12-13


作者:小傅哥
博客: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)境

  1. JDK 1.8.0
  2. byte-buddy 1.10.9
  3. byte-buddy-agent 1.10.9
  4. 本章涉及源碼在: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 = 350L )
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", 350L).build())
        .make();
  • 這部分基本是Byte-buddy的模板方法,通過核心API;subclass、name、methodintercept、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 = 350L
)
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)注、點贊、分享)!

CodeGuide | 程序員編碼指南

    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多