亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享

用ByteBuddy編寫一個簡單的Agent

2024-08-22 10:06:13
13
0

一、從認識ByteBuddy開始

  在之前的博客當中我們了解了Java Agent的一些基本概念和如何編寫一個簡單的Java Agent,但是在之前的博客中所使用的Agent編寫方法還是相對原始和繁瑣的。在原先的邏輯中我們是使用Instrument直接進行二進制碼操作和修改,這種方式要求使用者對Java class文件格式的相關知識能夠了然于胸,簡單來說就是需要做到人肉翻譯二進制文件這樣一個非人操作。為了進一步簡化編寫Java Agent的復雜度,這里我們要介紹下面這樣一款字節碼處理利器——ByteBuddy

  ByteBuddy是一個能夠在Java應用程序運行時用于創建和修改Java類的代碼生成和操作類庫,而這種處理能力是不需要編譯器參與的。從官網的介紹中可以發現,ByteBuddy是基于另一款字節碼操作神器ASM創造出來的,但是相比ASM的高使用門檻(仍然需要對Java字節碼有一定的了解),ByteBuddy使用起來會顯得更為簡單便捷。由于ByteBuddy提供了一系列完善且便捷的API,使用者可以在不需要了解Java字節碼和class文件格式的情況下很方便地進行字節碼操作(通過使用Java Agent或者在程序構建時完成對應的操作)。

二、編寫一個簡單的Java Agent——方法耗時統計

  從上面的描述中我們可以了解到,ByteBuddy的誕生并非單純為了創建Java Agent,我們只是借助了ByteBuddy提供的API來生成更易維護的Java Agent,下面我們通過一個簡單的例子來了解一下如何使用ByteBuddy來編寫一個Java Agent。

  下面我們要編寫的Java Agent主要是用于進行方法執行的耗時統計,參考以往使用AOP方式的思路,我們需要進行以下處理:

  • 指定需要攔截處理的對象(可以是類、方法或者被注解的元素);
  • 明確如何處理攔截的對象;

  在Java Agent當中所有關于字節碼的操作都需要通過Instrumentation來進行,為了完成上面的兩個操作和關于Instrumentation的操作,ByteBuddy提供了AgentBuilder類來提供所需的API接口。我們借助下面的例子看一下AgentBuilder提供的API接口。

DemoAgent(示例Agent)

代碼語言:javascript
復制
public class DemoAgent {

    /**
     * 在主線程啟動之前進行處理
     *
     * @param agentArgs       代理請求參數
     * @param instrumentation 插樁
     */
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        handleInstrument(instrumentation);
    }

    /**
     * 進行插樁處理
     *
     * @param instrumentation 待處理樁
     */
    private static void handleInstrument(Instrumentation instrumentation) {
        new AgentBuilder.Default()
                .type(ElementMatchers.nameEndsWith("App"))
                .transform((builder, type, classLoader, module) -> builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(TimeInterceptor.class)))
                .installOn(instrumentation);
    }

}

TimeInterceptor(方法執行耗時統計攔截器)

代碼語言:javascript
復制
/**
 * 時間統計攔截器,虛擬機維度的aop
 *
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/12/30 10:31 上午
 */
public class TimeInterceptor {

    /**
     * 進行方法攔截, 注意這里可以對所有修飾符的修飾的方法(包含private的方法)進行攔截
     *
     * @param method   待處理方法
     * @param callable 原方法執行
     * @return 執行結果
     */
    @RuntimeType
    public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
        long start = System.currentTimeMillis();
        System.out.println("agent test: before method invoke! Method name: " + method.getName());
        try {
            return callable.call();
        } catch (Exception e) {
            // 進行異常信息上報
            System.out.println("方法執行發生異常" + e.getMessage());
            throw e;
        } finally {
            System.out.println("agent test: after method invoke! Method name: " + method.getName());
            System.out.println(method + ": took " + (System.currentTimeMillis() - start) + " millisecond");
        }
    }
}

  在上面的示例中我們使用了AgentBuilder的默認實現類(這里不難發現創建者模式的身影,由于不是關注的重點這里暫時不提),通過AgentBuilder的默認實現類完成了以下三件事件:

  1. 通過Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher);方法,指定了當前Agent需要攔截處理的對象,在本例中需要處理的對象為所有名稱以App結尾的類型;
  2. 通過Extendable transform(Transformer transformer);方法明確了需要如何處理被攔截的對象,這里使用了lambda方式來簡寫了對于Transformer#transform方法的實現。在實現的過程中通過builder.method()進一步明確需要處理的方法,在本例中會處理符合上一個攔截條件的所有方法,接著通過intercept()方法和MethodDelegation來注入關于被攔截方法的另一種實現方法,本例中通過TimeInterceptor來完成對于方法的執行耗時統計。看到這里是否會感覺和代理模式(或者說我們常用的AOP)有些類似,尤其是TimeInterceptor當中的處理邏輯,只是在這一過程中并沒有使用反射機制,這也是使用ByteBuddy的一個優勢;
  3. 最后,在完成了對于攔截對象的指定和對象處理邏輯的編寫后,通過ResettableClassFileTransformer installOn(Instrumentation instrumentation);完成Instrumentation裝載ClassFileTransformer(即上面關于文件修改的邏輯)的邏輯;

  從上面的流程可以看出,本質上AgentBuilder幫助我們完成了關于ClassFileTransformer的實現和裝載邏輯。和原先直接編寫一個ClassFileTransformer然后修改其中的二進制文件數據相比,使用AgentBuilder來會讓我們對于整個的處理邏輯更加明確和專注,在編寫的過程我們只需要關注所需要修改的對象和修改的邏輯,其余只需要通過API就可以完成所有的操作。

三、總結

  本文更多在于介紹ByteBuddy的概要和使用ByteBuddy創建Java Agent的使用流程,對于ByteBuddy具體的原理這里不做過多的說明,在后續的篇章中會進行具體的介紹。

0條評論
0 / 1000
1****n
10文章數
0粉絲數
1****n
10 文章 | 0 粉絲

用ByteBuddy編寫一個簡單的Agent

2024-08-22 10:06:13
13
0

一、從認識ByteBuddy開始

  在之前的博客當中我們了解了Java Agent的一些基本概念和如何編寫一個簡單的Java Agent,但是在之前的博客中所使用的Agent編寫方法還是相對原始和繁瑣的。在原先的邏輯中我們是使用Instrument直接進行二進制碼操作和修改,這種方式要求使用者對Java class文件格式的相關知識能夠了然于胸,簡單來說就是需要做到人肉翻譯二進制文件這樣一個非人操作。為了進一步簡化編寫Java Agent的復雜度,這里我們要介紹下面這樣一款字節碼處理利器——ByteBuddy

  ByteBuddy是一個能夠在Java應用程序運行時用于創建和修改Java類的代碼生成和操作類庫,而這種處理能力是不需要編譯器參與的。從官網的介紹中可以發現,ByteBuddy是基于另一款字節碼操作神器ASM創造出來的,但是相比ASM的高使用門檻(仍然需要對Java字節碼有一定的了解),ByteBuddy使用起來會顯得更為簡單便捷。由于ByteBuddy提供了一系列完善且便捷的API,使用者可以在不需要了解Java字節碼和class文件格式的情況下很方便地進行字節碼操作(通過使用Java Agent或者在程序構建時完成對應的操作)。

二、編寫一個簡單的Java Agent——方法耗時統計

  從上面的描述中我們可以了解到,ByteBuddy的誕生并非單純為了創建Java Agent,我們只是借助了ByteBuddy提供的API來生成更易維護的Java Agent,下面我們通過一個簡單的例子來了解一下如何使用ByteBuddy來編寫一個Java Agent。

  下面我們要編寫的Java Agent主要是用于進行方法執行的耗時統計,參考以往使用AOP方式的思路,我們需要進行以下處理:

  • 指定需要攔截處理的對象(可以是類、方法或者被注解的元素);
  • 明確如何處理攔截的對象;

  在Java Agent當中所有關于字節碼的操作都需要通過Instrumentation來進行,為了完成上面的兩個操作和關于Instrumentation的操作,ByteBuddy提供了AgentBuilder類來提供所需的API接口。我們借助下面的例子看一下AgentBuilder提供的API接口。

DemoAgent(示例Agent)

代碼語言:javascript
復制
public class DemoAgent {

    /**
     * 在主線程啟動之前進行處理
     *
     * @param agentArgs       代理請求參數
     * @param instrumentation 插樁
     */
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        handleInstrument(instrumentation);
    }

    /**
     * 進行插樁處理
     *
     * @param instrumentation 待處理樁
     */
    private static void handleInstrument(Instrumentation instrumentation) {
        new AgentBuilder.Default()
                .type(ElementMatchers.nameEndsWith("App"))
                .transform((builder, type, classLoader, module) -> builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(TimeInterceptor.class)))
                .installOn(instrumentation);
    }

}

TimeInterceptor(方法執行耗時統計攔截器)

代碼語言:javascript
復制
/**
 * 時間統計攔截器,虛擬機維度的aop
 *
 * @author brucebat
 * @version 1.0
 * @since Created at 2021/12/30 10:31 上午
 */
public class TimeInterceptor {

    /**
     * 進行方法攔截, 注意這里可以對所有修飾符的修飾的方法(包含private的方法)進行攔截
     *
     * @param method   待處理方法
     * @param callable 原方法執行
     * @return 執行結果
     */
    @RuntimeType
    public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable) throws Exception {
        long start = System.currentTimeMillis();
        System.out.println("agent test: before method invoke! Method name: " + method.getName());
        try {
            return callable.call();
        } catch (Exception e) {
            // 進行異常信息上報
            System.out.println("方法執行發生異常" + e.getMessage());
            throw e;
        } finally {
            System.out.println("agent test: after method invoke! Method name: " + method.getName());
            System.out.println(method + ": took " + (System.currentTimeMillis() - start) + " millisecond");
        }
    }
}

  在上面的示例中我們使用了AgentBuilder的默認實現類(這里不難發現創建者模式的身影,由于不是關注的重點這里暫時不提),通過AgentBuilder的默認實現類完成了以下三件事件:

  1. 通過Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher);方法,指定了當前Agent需要攔截處理的對象,在本例中需要處理的對象為所有名稱以App結尾的類型;
  2. 通過Extendable transform(Transformer transformer);方法明確了需要如何處理被攔截的對象,這里使用了lambda方式來簡寫了對于Transformer#transform方法的實現。在實現的過程中通過builder.method()進一步明確需要處理的方法,在本例中會處理符合上一個攔截條件的所有方法,接著通過intercept()方法和MethodDelegation來注入關于被攔截方法的另一種實現方法,本例中通過TimeInterceptor來完成對于方法的執行耗時統計。看到這里是否會感覺和代理模式(或者說我們常用的AOP)有些類似,尤其是TimeInterceptor當中的處理邏輯,只是在這一過程中并沒有使用反射機制,這也是使用ByteBuddy的一個優勢;
  3. 最后,在完成了對于攔截對象的指定和對象處理邏輯的編寫后,通過ResettableClassFileTransformer installOn(Instrumentation instrumentation);完成Instrumentation裝載ClassFileTransformer(即上面關于文件修改的邏輯)的邏輯;

  從上面的流程可以看出,本質上AgentBuilder幫助我們完成了關于ClassFileTransformer的實現和裝載邏輯。和原先直接編寫一個ClassFileTransformer然后修改其中的二進制文件數據相比,使用AgentBuilder來會讓我們對于整個的處理邏輯更加明確和專注,在編寫的過程我們只需要關注所需要修改的對象和修改的邏輯,其余只需要通過API就可以完成所有的操作。

三、總結

  本文更多在于介紹ByteBuddy的概要和使用ByteBuddy創建Java Agent的使用流程,對于ByteBuddy具體的原理這里不做過多的說明,在后續的篇章中會進行具體的介紹。

文章來自個人專欄
文章 | 訂閱
0條評論
0 / 1000
請輸入你的評論
0
0