ByteBuddy入門筆記

eacape發表於2023-04-27

簡介

Byte Buddy是一個開源的Java位元組碼操作庫,它允許程式在執行時生成和修改Java類的位元組碼,而無需使用編譯器。Byte Buddy主要用於建立和操作Java位元組碼,但也可以用於建立任意類,並且不限於實現用於建立執行時代理的介面。

Byte Buddy提供了一種方便的API,使使用者可以使用Java代理或在構建過程中手動更改類。Byte Buddy還支援檢測和修改現有Java位元組碼的工具,例如自動編碼、檢測程式碼生成實用程式、轉換現有程式碼等。

Byte Buddy的流式API允許使用者從Byte Buddy例項出發,完成所有的操作和資料定義。此外,Byte Buddy不僅限於建立子類和操作類,還可以轉換現有程式碼。Byte Buddy還提供了一個方便的API,用於定義所謂的 Java 代理,該代理允許在任何 Java 應用程式的執行期間進行程式碼轉換。

總之,Byte Buddy是一個強大的工具,可以方便地操作和生成Java位元組碼,從而簡化Java開發過程。

各類增強工具對比

效能對比

類的生成策略面臨一個權衡,Byte Buddy的主要重點在於以最少的執行時間生成程式碼。

這是面對類生成,介面實現等行為的不同增強工具的效能對比,總體看起來bytebuddy的效能沒有特殊的地方,沒有惡化。

優缺點

  • java proxy6

    Java類庫附帶一個代理工具包,該工具包允許建立實現一組給定介面的類。這個內建的代理使用很方便,但功能非常有限。比如代理只能面對一個已經存在的介面,

    但是對類進行擴充套件的時候,proxy辦不到

  • cglib

    太早了,沒人維護了。

    該程式碼生成庫是在最初幾年的Java實現,它也不幸的是沒有與Java平臺的發展跟上。儘管如此,cglib仍然是一個功能非常強大的庫,但是它的積極開發卻變得相當模糊。因此,它的許多使用者都離開了cglib。

  • javassist

    自帶了一個相比javac弱小編譯器,而且動態生成位元組碼時容易出錯。

    該庫帶有一個編譯器,該編譯器採用包含Java原始碼的字串,這些字串在應用程式執行時會轉換為Java位元組程式碼。因為Java原始碼顯然是描述Java類的一種很好的方法,所以這是非常雄心勃勃的,並且從原則上講是個好主意。

    但是,Javassist編譯器在功能上無法與javac編譯器相提並論,並且在動態組成字串以實現更復雜的邏輯時,容易出錯。此外,Javassist附帶了一個代理庫,該代理庫與JCL的代理實用程式相似,但是允許擴充套件類,並且不限於介面。

創造一個類

程式碼初體驗

在進行Byte Buddy之前先建立一個專案,然後引入相關依賴:

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.10.18</version>
</dependency>

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

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.8.2</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
</dependency>

測試類如下:

@Slf4j
public class CreateClassTest {
    private static String path;

    /**
     * 每個測試方法執行前獲取CreateClassTest的路徑
     */
    @BeforeAll
    private static void before(){
        path = CreateClassTest.class.getClassLoader().getResource("").getPath();
        System.out.println(path);

    }
    /**
     * 生成一個類
     */
    @Test
    public void create() throws IOException {
        //Unloaded代表生成位元組碼還未載入到jvm
        DynamicType.Unloaded<Object> unloaded = new ByteBuddy()
            //指定父類
            .subclass(Object.class)
            .make();
        //獲取生成類的位元組碼
        byte[] bytes = unloaded.getBytes();
    }
}
  • DynamicType:在執行時建立的動態型別,通常作為應用DynamicType.Builder或.AuxiliaryType的結果。
  • Unloaded:尚未被給定ClassLoader載入的動態型別。

    interface Unloaded<T> extends DynamicType

    型別引數: -由該動態型別實現的最具體的已知載入型別,通常是型別本身、介面或直接的超類。

  • ByteBuddy:在建立時,Byte Buddy會嘗試發現當前JVM的版本。如果不可能,則建立與Java 6相容的類檔案。
  • subclass:用於建立所提供型別的子類,如果提供的型別是介面,則建立實現此介面型別的新類。

    當擴充套件一個類時,Byte Buddy模仿子類型別的所有可見建構函式。任何建構函式都被實現為只呼叫其具有相同簽名的超型別建構函式。另一種行為可以透過subclass(Class, ConstructorStrategy)提供顯式的ConstructorStrategy來指定。 注意:如果所提供的型別宣告瞭型別變數或所有者型別,則此方法以泛型狀態實現它們。

    /**
     * 透過構造器策略生成一個類
     */
    @Test
    public void createByConstructorStrategy() throws IOException {
        //Unloaded代表生成位元組碼還未載入到jvm
        DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
            //指定父類
            .subclass(CreateByConstructorStrategyEntity.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
            .make();
        //獲取生成類的位元組碼
        byte[] bytes = unloaded.getBytes();
    }

例項化

@Test
public void create() throws Exception {
    //Unloaded代表生成位元組碼還未載入到jvm
    DynamicType.Unloaded<Object> unloaded = new ByteBuddy()
        //指定父類
        .subclass(Object.class)
        .make();
    Class<?> loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method method = loaded.getMethod("toString");
    System.out.println(method.invoke(loaded.newInstance()));
}

構造器策略

  • NO_CONSTRUCTORS:此策略不新增建構函式
  • DEFAULT_CONSTRUCTOR:這個策略是新增一個預設建構函式來呼叫它的超型別預設建構函式。
  • IMITATE_SUPER_CLASS:這種策略是新增插裝型別的超類的所有建構函式,其中每個建構函式都直接呼叫其簽名等效的超類建構函式。
  • IMITATE_SUPER_CLASS_PUBLIC:這種策略是新增插裝型別的超類的所有建構函式,其中每個建構函式都直接呼叫其簽名等效的超類建構函式,只新增公共建構函式。
  • IMITATE_SUPER_CLASS_OPENING:這種策略是新增插裝型別的超類的所有建構函式,其中每個建構函式都直接呼叫其簽名等效的超類建構函式,為超類的任何可呼叫且宣告為public的建構函式新增建構函式。

上面是Byte Buddy註釋的直譯,具體的一些解釋會在下面測試中解釋。

為了測試以上策略建立一個類作為生成類得超類,如下:

public class CreateByConstructorStrategyEntity {
    private Long id;
    private String name;
    private String content;

    public CreateByConstructorStrategyEntity() {
    }

    protected CreateByConstructorStrategyEntity(Long id) {
        this.id = id;
    }

    private CreateByConstructorStrategyEntity(String name) {
        this.name = name;
    }

    public CreateByConstructorStrategyEntity(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    public CreateByConstructorStrategyEntity(Long id, String name, String content) {
        this.id = id;
        this.name = name;
        this.content = content;
    }
}

然後用用上面五種策略分別生成新類,並儲存到檔案中

@Test
public void createByConstructorStrategy() throws IOException {
    //Unloaded代表生成位元組碼還未載入到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //指定父類
        .subclass(CreateByConstructorStrategyEntity.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
        .make();
    unloaded.saveIn(new File(path));
}
  • NO_CONSTRUCTORS:不生成構造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$3c1yosow extends CreateByConstructorStrategyEntity {
    }
  • DEFAULT_CONSTRUCTOR:給一個預設構造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$sEqzBgYH extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$sEqzBgYH() {
        }
    }
  • IMITATE_SUPER_CLASS:新增超類所有可訪問的構造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k(Long var1, String var2, String var3) {
            super(var1, var2, var3);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k(Long var1, String var2) {
            super(var1, var2);
        }
    
        protected CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k(Long var1) {
            super(var1);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$w3TC5Z4k() {
        }
    }
  • IMITATE_SUPER_CLASS_PUBLIC:新增超類所有Public修飾的構造器

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS(Long var1, String var2, String var3) {
            super(var1, var2, var3);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS(Long var1, String var2) {
            super(var1, var2);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS() {
        }
    }
    
  • IMITATE_SUPER_CLASS_OPENING:新增超類所有可訪問的構造器並轉換為Public

    package com.eacape.bytebuddy;
    
    public class CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn extends CreateByConstructorStrategyEntity {
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn(Long var1, String var2, String var3) {
            super(var1, var2, var3);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn(Long var1, String var2) {
            super(var1, var2);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn(Long var1) {
            super(var1);
        }
    
        public CreateByConstructorStrategyEntity$ByteBuddy$wMHt2jMn() {
        }
    }

檔案儲存

生成類位元組碼可以進行儲存unloaded.saveIn(new File(path));,也就是將你生成的位元組碼以.class檔案儲存到相應的位置,但,繼承jdk原生類和繼承自定義類的儲存位置不同。

  • 繼承jdk原生類,檔案會被儲存到net.bytebuddy.renamed路徑下然後加超類的路徑

    net.bytebuddy.renamed.java.lang.Object$ByteBuddy$i0ivrOhL
  • 繼承使用者自定義類,檔案被儲存和生成類同級路徑下

    com.eacape.bytebuddy.CreateByConstructorStrategyEntity$ByteBuddy$AnM1yaqS

命名策略

public void createWithNameStrategy() throws IOException {
    NamingStrategy.SuffixingRandom suffixingRandom = new NamingStrategy.SuffixingRandom("eacape");
    //Unloaded代表生成位元組碼還未載入到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(suffixingRandom)
        //指定父類
        .subclass(CreateByConstructorStrategyEntity.class)
        .make();
    unloaded.saveIn(new File(path));
}

生成的類名為CreateByConstructorStrategyEntity$eacape$OnHkVvgk,如果不指定生成策略則生成的類名如下

$$ 超類類名 + ByteBuddy + 8位隨機字元 $$

增加SuffixingRandom則自定義的字尾將替換ByteBuddy,如果將命名策略改為PrefixingRandom則命名結果如下:

也可以直接指定類名

@Test
public void createWithName() throws IOException {
    //Unloaded代表生成位元組碼還未載入到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //指定父類
        .subclass(CreateByConstructorStrategyEntity.class)
        .name("cn.baidu.NewCreateByConstructorStrategyEntity")
        .make();
    unloaded.saveIn(new File(path));
}

自定義命名策略

@Test
public void createWithSelfDefineNameStrategy() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //使用自定義命名策略
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .subclass(CreateByConstructorStrategyEntity.class)
        .make();
    String canonicalName = unloaded.load(this.getClass().getClassLoader()).getLoaded().getCanonicalName();
    System.out.println(canonicalName);
}
//結果
top.eacape.CreateByConstructorStrategyEntity

位元組碼注入

也可以將位元組碼注入到已有的jar中,程式碼和操作結果如下

@Test
public void createWithInjectJar() throws IOException {
    //Unloaded代表生成位元組碼還未載入到jvm
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        //是否檢驗生成位元組碼的正確性
        .with(TypeValidation.of(true))
        //指定父類
        .subclass(CreateByConstructorStrategyEntity.class)
        .name("cn.baidu.NewCreateByConstructorStrategyEntity")
        .make();
    unloaded.inject(new File("E:\\xxx\\arthas-demo.jar"));
}

類載入策略

DynamicType.Unloaded,代表一個尚未載入的類,顧名思義,這些型別不會載入到 Java 虛擬機器中,它僅僅表示建立好了類的位元組碼,透過 DynamicType.Unloaded 中的 getBytes 方法你可以獲取到該位元組碼。

在應用程式中,可能需要將該位元組碼儲存到檔案,或者注入的現在的 jar 檔案中,因此該型別還提供了一個 saveIn(File) 方法,可以將類儲存在給定的資料夾中; inject(File) 方法將類注入到現有的 Jar 檔案中,另外你只需要將該位元組碼直接載入到虛擬機器使用,你可以透過 ClassLoadingStrategy 來載入。

如果不指定ClassLoadingStrategy,Byte Buffer根據你提供的ClassLoader來推匯出一個策略,內建的策略定義在列舉ClassLoadingStrategy.Default中

  • WRAPPER:建立一個新的Wrapping類載入器
  • CHILD_FIRST:類似上面,但是子載入器優先負責載入目標類
  • INJECTION:利用反射機制注入動態型別
 Class<?> dynamicClass = dynamicType
                .load(Object.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();

我們使用 WRAPPER 策略來載入適合大多數情況的類,這樣生產的動態類不會被ApplicationClassLoader載入到,不會影響到專案中已經存在的類getLoaded 方法返回一個 Java Class 的例項,它就表示現在載入的動態類

增強一個類

修改方法

@Test
public void methodFixedValue() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .subclass(CreateByConstructorStrategyEntity.class)
        //對toString方法進行攔截
        .method(named("toString"))
        //返回固定值
        .intercept(FixedValue.value("Hello ByteBuddy"))
        .make();
    unloaded.saveIn(new File(path));
}

插入方法

重新建立一個新的實體類用於測試

public class BaseEntity {
}
@Test
public void insertMethod() throws IOException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        .defineMethod("init", String.class, Modifier.PUBLIC)
        .withParameters(Integer.class, String.class)
        .intercept(FixedValue.value("my return value"))
        .make();
    Class loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method init = loaded.getMethod("init", Integer.class, String.class);
    String result = (String) init.invoke(loaded.newInstance(), null, null);
    System.out.println(result);
}

// 輸出結果 my return value

插入屬性

@Test
public void insertField() throws IOException {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .rebase(BaseEntity.class)
        .defineField("age", Integer.class, Modifier.PRIVATE)
        .make();
    unloaded.saveIn(new File(path));
}

增強類

增強一個類有兩種方式redefine&rebase,與subclass不同的是這兩種方式是在原有類的基礎上修改,而subclass是生成一個子類。

  • rebase:會保留所有被變基類的方法實現。Byte Buddy 會用相容的簽名複製所有方法的實現為一個私有的重新命名過的方法, 而不像類重定義時丟棄覆寫的方法。用這種方式的話,不存在方法實現的丟失,而且變基的方法可以透過呼叫這些重新命名的方法, 繼續呼叫原始的方法。
  • redifine:允許透過新增欄位和方法或者替換已存在的方法實現來修改已存在的類。 但是,如果方法的實現被另一個實現所替換,之前的實現就會丟失。

使用rebase將之前測試的實體類增加一個getId方法並返回固定值0

public class CreateByConstructorStrategyEntity {
    .....
    public Long getId() {
        return 0L;
    }
    .....
}

然後以rebase的方式將其修改,使getId方法的返回值修改

@Test
public void dynamicEnhanceByRebase() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .rebase(CreateByConstructorStrategyEntity.class)
        .method(ElementMatchers.<MethodDescription>named("getId"))
        .intercept(FixedValue.value(50L))
        .make();
    unloaded.saveIn(new File(path));
}

修改結果如下,getId的返回值已經由0變為50

用idea直接檢視沒有什麼問題,但是檢視位元組碼發現這個檔案中還有一個以getId開頭的方法,它的返回值為0,代表getId的原始方法

反觀使用redefine就沒有這種效果

@Test
public void dynamicEnhanceByRedefine() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .redefine(CreateByConstructorStrategyEntity.class)
        .method(ElementMatchers.<MethodDescription>named("getId"))
        .intercept(FixedValue.value(50L))
        .make();
    unloaded.saveIn(new File(path));
}

匹配方法

如上Byte Buddy提供了各種匹配方法,可以讓我們像sql中的where條件一樣去靈活的匹配各種方法

@Test
public void dynamicEnhanceMatchMethod() throws IOException {
    DynamicType.Unloaded<CreateByConstructorStrategyEntity> unloaded = new ByteBuddy()
        .with(new NamingStrategy.AbstractBase() {
            protected String name(TypeDescription superClass) {
                return "top.eacape." + superClass.getSimpleName();
            }
        })
        .redefine(CreateByConstructorStrategyEntity.class)
        //攔截方法名為getName的方法或者方法名含有Content並且返回值為String型別的方法
        .method(ElementMatchers.<MethodDescription>named("getName")
            .or(
               nameContains("Content")
                    .and(returns(String.class))
            )
        )
        .intercept(FixedValue.value("Hello"))
        .make();
    unloaded.saveIn(new File(path));
}

委託方法

在大多數情況下,方法返回一個固定值當然是不夠的。為了更好的靈活性,Byte Buddy 提供了MethodDelegation(方法委託)實現, 它在對方法呼叫做出反應時提供最大程度的自由。一個方法委託定義了動態建立的類方法,到另外一個可能存在於動態型別之外的方法的任何呼叫。 這樣,動態類的邏輯可以用簡單的 Java 表示,僅透過程式碼生成就可以與另外的方法繫結。

實現委託方法有兩種,一種是靜態方法委託,一種是成員方法委託,具體程式碼如下

public class BaseEntity {
    public void a(Long id){
        System.out.println("hello");
    }

    public void b(Long id){
        System.out.println("hello");
    }
}
public class BaseEntityInterceptor {
    public static void ADelegation(Long id) {
        System.out.println(id + " name is " + UUID.randomUUID());
    }

    public void BDelegation(Long id) {
        System.out.println(id + " name is " + UUID.randomUUID());
    }
}
@Test
public void methodDelegation() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        //對a方法進行靜態方法委託增強
        .method(named("a"))
        .intercept(MethodDelegation.to(BaseEntityInterceptor.class))
        //對b方法進行成員方法委託增強
        .method(named("b"))
        .intercept(MethodDelegation.to(new BaseEntityInterceptor()))
        .make();
    Class loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method a = loaded.getMethod("a", Long.class);
    a.invoke(loaded.newInstance(),2L);
    Method b = loaded.getMethod("b", Long.class);
    b.invoke(loaded.newInstance(),4L);
}

//結果如下
2 name is b075a124-072a-4163-bf2d-130eb299f96c
4 name is 08e08e11-9698-4964-a3b0-2720b0162a4b

上面的委託方式看起來還是不夠靈活,日常最常使用的委託方式是透過註解進行引數繫結。

註解說明
@Argument繫結單個引數
@AllArguments繫結所有引數的陣列
@This當前被攔截的、動態生成的那個物件
@Super當前被攔截的、動態生成的那個物件,不會繼承原有的類
@Origin可以繫結到以下型別的引數: - Method 被呼叫的原始方法 - Constructor 被呼叫的原始構造器 - Class 當前動態建立的類 - MethodHandleMethodTypeString 動態類的toString()的返回值 - int 動態方法的修飾符
@DefaultCall呼叫預設方法而非super的方法
@SuperCall用於呼叫父類版本的方法
@RuntimeType可以用在返回值、引數上,提示ByteBuddy禁用嚴格的型別檢查
@Empty注入引數的型別的預設值
@StubValue注入一個存根值。對於返回引用、void的方法,注入null;對於返回原始型別的方法,注入0
@FieldValue注入被攔截物件的一個欄位的值
@Morph類似於@SuperCall,但是允許指定呼叫引數

下面使用以上註解對BaseEntity.a進行簡單測試,另外當註解這種方式和之前的委託方法同時存在時,會優先選擇前者。

public class BaseEntityInterceptor {
//    public static void ADelegation(Long id) {
//        System.out.println(id + " name is " + UUID.randomUUID());
//    }
//
//    public void BDelegation(Long id) {
//        System.out.println(id + " name is " + UUID.randomUUID());
//    }

    @RuntimeType
    public void runtimeAspect(
        @This Object targetObject, 
        @Super Object superObject,
        @Origin Method originMethod,
        @AllArguments Object[] args,
        @SuperCall Callable<?> call
    ) {
        System.out.println(targetObject);
        System.out.println(superObject);
        System.out.println(originMethod);
        System.out.println(Arrays.toString(args));
        try {
            long start = System.currentTimeMillis();
            call.call();
            System.out.println(System.currentTimeMillis() - start + "ms");
        } catch (Exception exception) {
            exception.printStackTrace();
        }
    }
}
/**
 *方法委託
 */
@Test
public void methodDelegation() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        //對a方法進行靜態方法委託增強
        .method(named("a"))
        .intercept(MethodDelegation.to(new BaseEntityInterceptor()))
        .make();
    Class loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method a = loaded.getMethod("a", Long.class);
    a.invoke(loaded.newInstance(),4L);
}
// 結果
com.eacape.bytebuddy.BaseEntity$ByteBuddy$r0NYPVQb@2fc0cc3
com.eacape.bytebuddy.BaseEntity$ByteBuddy$r0NYPVQb@2fc0cc3
public void com.eacape.bytebuddy.BaseEntity.a(java.lang.Long)
[4]
hello
0ms

前面示例中,使用 @SuperCall 註解注入的 Callable 引數來呼叫目標方法時,是無法動態修改引數的,如果想要動態修改引數,則需要用到 @Morph 註解以及一些繫結操作,示例如下:

@Test
public void methodDelegationByMorph() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        .method(named("getId"))
        .intercept(MethodDelegation
            .withDefaultConfiguration()
            // 要用@Morph註解之前,需要透過 Morph.Binder 告訴 Byte Buddy
            // 要注入的引數是什麼型別
            .withBinders(Morph.Binder.install(MorphCallable.class))
            .to(new BaseEntityInterceptor())
        )
        .make();
    Class<?> loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    Method a = loaded.getMethod("getId", Long.class);
    a.invoke(loaded.newInstance(), 100L);
}

這裡的 Interceptor 會使用 @Morph 註解注入一個 MorphCallable 物件作為引數,然後透過該 MorphCallable物件呼叫目標方法,如下所示:

@RuntimeType
public void morphAspect(
    @This Object target,
    @Super Object superO,
    @Origin Method method,
    @AllArguments Object[] args,
    @Morph MorphCallable call
) {
    try {
        long start = System.currentTimeMillis();
        if (superO instanceof BaseEntity && "getId".equals(method.getName())){
            args[0] = (Long)args[0] + 1;
        }
        Object res = call.call(args);
        System.out.println(res);
        System.out.println(System.currentTimeMillis() - start + "ms");
    } catch (Exception exception) {
        exception.printStackTrace();
    }
}
public class BaseEntity {
    public Long getId(Long id){
        System.out.println(id);
        return 308843 + id;
    }
}
public interface MorphCallable {

    /**
     * Callable.
     * @Description 呼叫方法
     * @param args the args
     * @return object
     */
    Object call(Object[] args);
}

輸出結果為

100
308943
0ms

構造器方法

public class BaseEntity {

    public BaseEntity() {
        System.out.println("BaseEntity例項化成功");
    }
}
@RuntimeType
public void constructorAspect(
    @This Object target
) {
    System.out.println(target + ":例項化後置操作");
}
@Test
public void constructorDelegation() throws Exception {
    DynamicType.Unloaded<BaseEntity> unloaded = new ByteBuddy()
        .subclass(BaseEntity.class)
        .constructor(any())
        .intercept(
            //在構造器方法執行完成之後進行攔截
            SuperMethodCall.INSTANCE.andThen(
                MethodDelegation.to(new BaseEntityInterceptor())
            )
        )
        .make();
    Class<?> loaded = unloaded.load(this.getClass().getClassLoader()).getLoaded();
    loaded.newInstance();
}
//結果
BaseEntity例項化成功
com.eacape.bytebuddy.BaseEntity$ByteBuddy$8116eKW8@1613674b:例項化後置操作

參考

https://bytebuddy.net/#/tutorial

https://blog.csdn.net/wanxiaoderen/article/details/106544773

https://www.bilibili.com/video/BV1G24y1a7bd

相關文章