ByteBuddy(八)—Around Advice

除了OnMethodEnterOnMethodExit Advice之外,ByteBuddy還支持Around Advice
Around Advice通過(guò)方法委托將Advice代碼添加到函數(shù)方法的開(kāi)始和結(jié)束。

image.png

本章使用兩個(gè)Advice代碼:

PerfObjectReturnInterceptor.java
PerfVoidReturnInterceptor.java

這是功能代碼

public class DataProducer{

    public void create(){
        logger.info("create data");
    }

    public String createData(){
        String uniqueId = UUID.randomUUID().toString();
        return new String(
                  Base64.getEncoder().encode(uniqueId.getBytes()), 
                  Charset.forName("UTF-8"));
    }

}

create方法在屏幕上打印狀態(tài)字符串"create data"。
createData方法返回base64編碼的UUID。

這是InterceptorPlugin.java方法的實(shí)現(xiàn)(3個(gè)方法不在解釋,不懂看之前的內(nèi)容)

public class InterceptorPlugin implements Plugin {
    @Override
    public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder,
                                        TypeDescription typeDescription,
                                        ClassFileLocator classFileLocator) {
        // 
    }
    @Override
    public void close() throws IOException {
        //
    }
    @Override
    public boolean matches(TypeDescription typeDefinitions) {
        //
    }
}

這是apply方法的實(shí)現(xiàn)

public DynamicType.Builder<?> apply(Builder<?> builder, 
                            TypeDescription typeDescription,
                            ClassFileLocator classFileLocator){
    return builder
          .method(ElementMatchers.named("create"))
               .intercept(MethodDelegation
                    .to(PerfVoidReturnInterceptor.class))
          .method(ElementMatchers.named("createData"))
              .intercept(MethodDelegation
                    .to(PerfObjectReturnInterceptor.class));
}

Around Advice create方法

apply方法中,調(diào)用builder.method的方法。
method方法使用ElementMatchers.named Advice匹配邏輯。
named方法使用"create"作為其參數(shù)值:

builder.method(ElementMatchers.named("create"))

因此,DataProducer.javacreate方法被選擇用于instrumentation
之后,調(diào)用builderintercept方法。
intercept方法的參數(shù)指定MethodDelegation.to(PerfVoidReturnInterceptor.class)

這些方法與onMethodEnteronMethodExit Advice完全不同。
首先,builder調(diào)用intercept方法來(lái)執(zhí)行檢測(cè),但不調(diào)用visit方法。
然后,檢測(cè)使用MethodDelegation,而不是Advice.to方法。
匹配邏輯是通過(guò)method方法實(shí)現(xiàn)的,但不是Advice方法鏈中的on方法。

PerfVoidReturnInterceptor.java

public class PerfVoidReturnInterceptor{

    public static Logger logger = Logger.getLogger(PerfVoidReturnInterceptor.class.getName());

    public static void aroundVoid(@SuperCall Runnable targetCode, @Origin Method method){
        long startTime = System.currentTimeMillis();
        logger.info("Method start");
        targetCode.run();
        logger.info("Methodend:" + method.getName() + "executiontime:" + (System.currentTimeMillis() - startTime));
    }
}

以下是實(shí)施的Around Advice的要求

  • Advice方法是一種靜態(tài)方法
  • Advice方法返回void。
  • Advice方法有一個(gè)java.lang.Runnable參數(shù),并用@SuperCall注解進(jìn)行注解。

在本例中,aroundVoid方法是Advice方法。
ByteBuddy將基于aroundVoid方法的內(nèi)容生成插入指令的代碼。
由于MethodDelegation@SuperCall注解,ByteBuddy創(chuàng)建了一個(gè)代理類來(lái)實(shí)現(xiàn)插入指令的代碼。

這個(gè)代碼的目的是測(cè)試業(yè)務(wù)代碼的運(yùn)行性能。

targetCode參數(shù)表示DataProducer.java的函數(shù)代碼的create方法。
插入指令的代碼將執(zhí)行以下指令:

1.將方法開(kāi)始時(shí)間存儲(chǔ)到startTime變量
2.記錄指示方法執(zhí)行開(kāi)始的消息
3.執(zhí)行功能代碼,在屏幕上打印狀態(tài)"create data"。
4.記錄一條消息,指示方法執(zhí)行結(jié)束,以及方法執(zhí)行時(shí)間(毫秒)。

啟動(dòng)maven build并執(zhí)行Main1.java
這是插入指令的代碼在屏幕上產(chǎn)生的結(jié)果:

11月 10, 2022 2:52:01 下午 com.wpixel.bytebuddy.chapter1.PerfVoidReturnInterceptor aroundVoid
信息: Method start
11月 10, 2022 2:52:01 下午 com.wpixel.bytebuddy.chapter1.DataProducer create$original$nNDKo4Mw
信息: create data
11月 10, 2022 2:52:01 下午 com.wpixel.bytebuddy.chapter1.PerfVoidReturnInterceptor aroundVoid
信息: Method end: create execution time: 140

方法委托不同于訪問(wèn)方法的字節(jié)碼。
為了更好地理解這一差異,讓我們看看生成的字節(jié)碼的Java源代:

public class DataProducer {
    public static Logger logger = Logger.getLogger(DataProducer.class.getName());

    public DataProducer() {
    }

    public void create() {
        PerfVoidReturnInterceptor.aroundVoid(new DataProducer$auxiliary$oGTr3aHb(this), cachedValue$4Pj3oKix$k5ssu03);
    }

    public String createData() {
        return (String)PerfObjectReturnInterceptor.aroundReturn(new DataProducer$auxiliary$wfWjMGB6(this), cachedValue$DokzLtVw$daoi2o0);
    }
}
class DataProducer$auxiliary$oGTr3aHb implements Runnable, Callable {
    private DataProducer argument0;

    public Object call() throws Exception {
        this.argument0.create$original$nNDKo4Mw$accessor$4Pj3oKix();
        return null;
    }

    public void run() {
        this.argument0.create$original$nNDKo4Mw$accessor$4Pj3oKix();
    }

    DataProducer$auxiliary$oGTr3aHb(DataProducer var1) {
        this.argument0 = var1;
    }
}

在生成的字節(jié)碼中,ByteBuddy創(chuàng)建一個(gè)名為create$original的新私有方法,并將原始create方法的方法內(nèi)容復(fù)制到該方法。

然后,ByteBuddy修改創(chuàng)建方法。
ByteBuddy聲明了一個(gè)名為auxiliary的內(nèi)部類。

這個(gè)內(nèi)部類是代理類。
代理類包含對(duì)函數(shù)代碼的引用:DataProducer.java代理類使用參數(shù)來(lái)存儲(chǔ)該引用,該引用通過(guò)代理類的構(gòu)造函數(shù)傳遞。
代理類實(shí)現(xiàn)了兩個(gè)Java接口:Java.lang.RunnableJava.util.concurrent.Callable

他們的派生方法調(diào)用并運(yùn)行通過(guò)argument0實(shí)例變量調(diào)用create$original方法。
然后,ByteBuddy在Advice方法的末尾使用PerfVoidReturnInterceptor.java調(diào)用aroundVoid方法,該方法調(diào)用targetCode.run方法。
其中run方法是java.lang.Runnable的實(shí)現(xiàn)。
可在auxiliary.oGTr3aHb中運(yùn)行。

盡管PerfVoidReturnInterceptor.java只為SuperCall參數(shù)指定了Runnable,但是ByteBuddy在其生成的字節(jié)碼中隱式地實(shí)現(xiàn)了RunnaableCallable接口。然而,如果函數(shù)方法返回void,則@SuperCall注解參數(shù)應(yīng)在Advice代碼中使用java.lang.Runnable。

關(guān)于createData方法的Advice

類似地,要攔截DataProducer.java的createData方法,這是InterceptorPlugin的代碼段。

 .method(ElementMatchers.named("createData"))
                .intercept(MethodDelegation
                        .to(PerfObjectReturnInterceptor.class));

createData方法是返回java.lang.String對(duì)象的方法。PerfObjectReturnInterceptor.java是createData方法的Advice代碼:

public class PerfObjectReturnInterceptor{
    public static Logger logger = Logger.getLogger(PerfObjectReturnInterceptor.class.getName());

    @RuntimeType 
    public static Object aroundReturn(
                    @SuperCall Callable<Object> targetCode,
                    @Origin Method method) throws Throwable{
        long startTime = System.currentTimeMillis();
        logger.info("Method start");
        Object result = targetCode.call();
        logger.info("Method end: " + method.getName() + " execution time: " + (System.currentTimeMillis() - startTime));
        return result;
    }
}

以下是實(shí)現(xiàn)支持aroundReturn函數(shù)方法的Advice的要求:

  • Advice方法是一種靜態(tài)方法。
  • advice方法返回Object實(shí)例,或函數(shù)方法返回類型之后的類型。
  • advice法有一個(gè)java.util.concurrent。可調(diào)用參數(shù),并用@SuperCall注解進(jìn)行注解。
  • Advice方法使用@RuntimeType注解進(jìn)行注解。

即使Advice代碼使用Callable接口而不是Runnable,ByteBuddy也會(huì)為create方法生成的類似字節(jié)碼,唯一的區(qū)別是DataProducer的createData方法。
java將調(diào)用PerfObjectReturnInterceptor.javaaroundReturn方法(在createData方法里),其第一個(gè)參數(shù)使用auxiliary的代理類實(shí)例:DataProducer$auxiliary$wfWjMGB6

class DataProducer$auxiliary$wfWjMGB6 implements Runnable, Callable {
    private DataProducer argument0;

    public Object call() throws Exception {
        return this.argument0.createData$original$qFMFHOxS$accessor$DokzLtVw();
    }

    public void run() {
        this.argument0.createData$original$qFMFHOxS$accessor$DokzLtVw();
    }

    DataProducer$auxiliary$wfWjMGB6(DataProducer var1) {
        this.argument0 = var1;
    }
}

aroundReturn方法可以更改函數(shù)代碼的返回值。
如在aroundReturn方法返回之前添加這行代碼:

result = "test";

然后,返回值將更改為test,它將替換base64編碼的UUID。
Advice方法要返回值,則需要@RuntimeType注解。

執(zhí)行項(xiàng)目

為了查看檢測(cè)過(guò)程的結(jié)果,請(qǐng)執(zhí)行maven build ,然后執(zhí)行Main1.java,將在屏幕上生成以下結(jié)果:

11月 10, 2022 3:26:56 下午 com.wpixel.bytebuddy.chapter1.PerfVoidReturnInterceptor aroundVoid
信息: Method start
11月 10, 2022 3:26:56 下午 com.wpixel.bytebuddy.chapter1.DataProducer create$original$qFMFHOxS
信息: create data
11月 10, 2022 3:26:56 下午 com.wpixel.bytebuddy.chapter1.PerfVoidReturnInterceptor aroundVoid
信息: Method end: create execution time: 109
11月 10, 2022 3:26:57 下午 com.wpixel.bytebuddy.chapter1.PerfObjectReturnInterceptor aroundReturn
信息: Method start
11月 10, 2022 3:26:57 下午 com.wpixel.bytebuddy.chapter1.PerfObjectReturnInterceptor aroundReturn
信息: Method end: createData execution time:15

結(jié)果表明,create方法的執(zhí)行時(shí)間為109毫秒,createData方法為15毫秒。

@Origin注解

注意到Advice方法有第二個(gè)參數(shù),它用@Origin注解。
請(qǐng)注意,此注解是net.bytebuddy.implementation.bind.annotation.Origin,
而不是net.bytebuddy.asm.Advice。
用于OnMethodEnterOnMethodExit Advice。
勸告Origin返回包含函數(shù)方法名稱的String值。
而Around Advice的@Origin 返回java.lang.reflect的實(shí)例。
表示函數(shù)方法的方法。
例如:

public static void aroundVoid(@SuperCall Runnable targetCode,
                          @Origin Method method){
    System.out.println(method.getName());
}

getName方法返回"create",這是函數(shù)方法的名稱。

MethodDelegation 和 Advice

MethodDelegation和Advice之間的區(qū)別:

  • DynamicType.Builderintercept方法使用MethodDelegation。
  • Advice由DynamicType.Builder的訪問(wèn)方法使用。
  • 當(dāng)使用visit方法時(shí),ByteBuddy生成將Advice代碼內(nèi)聯(lián)到函數(shù)代碼中的代碼。
  • MethodDelegationAdvice都可以應(yīng)用于OnMethodEnter或Exit通知,但Advice不能應(yīng)用Around Advice。

結(jié)論本章解釋了:

  • 如何為返回void的函數(shù)方法創(chuàng)建Around Advice
  • 如何為返回對(duì)象引用的函數(shù)方法創(chuàng)建Around Advice

bytebuddy書籍《Java Interceptor Development with ByteBuddy: Fundamental》

----END----

喜歡就點(diǎn)個(gè)??吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容