b bytebuddy 之agentbuilder 的使用&API詳解


使用中發現源碼中的高階用法根本看不懂,狠下心來試着結合文檔和源碼看下Agentbuilder。

  • 這篇文檔會忽略java agent的基礎知識,請自行百度。或者參考instrument
  • 對過於繁瑣的代碼,會說明意圖,細節等。
  • 首先會介紹用法的模板,涉及bytebuddy的各個類的作用,請參考bytebuddy簡單入門
  • 然後是按照類的結構來介紹Agentbuilder的功能。

希望我能寫完吧,太難了。

一、使用範式:創建一個agent

當一個應用變得龐大和模塊化時,使用java agent機制很合適。

定義transformer,每有一個類加載時,觸發transformer邏輯,對類進行匹配和修改。

bytebuddy提供了Agentbuilder,封裝了一系列API來新建一個 agent。
示例
假設我們定義了一個註解ToString,我們匹配所有標註@ToString的類,修改toString方法,讓其返回"transformed"

class ToStringAgent {
  public static void premain(String arguments, Instrumentation instrumentation) {
    new AgentBuilder.Default()
        .type(isAnnotatedWith(ToString.class))
        .transform(new AgentBuilder.Transformer() {
      @Override
      public DynamicType.Builder transform(DynamicType.Builder builder,
                                              TypeDescription typeDescription,
                                              ClassLoader classloader) {
        return builder.method(named("toString"))
                      .intercept(FixedValue.value("transformed"));
      }
    }).installOn(instrumentation);
  }
}
  • type 接受 一個ElementMatcher 匹配 ToString註解
  • transform 接受AgentBuilder.Transformer 來描述修改邏輯
// 這裏匹配類裏面的`toString`方法,使用`intercept`修改返回值
 public DynamicType.Builder transform(DynamicType.Builder builder,
                                              TypeDescription typeDescription,
                                              ClassLoader classloader) {
        return builder.method(named("toString"))
                      .intercept(FixedValue.value("transformed"));
      }
  • installOn ,將修改,應用到instrumentation中。

二、AgentBuilder 類結構

bytebuddy的API

2.1 嵌套的內部類

order Modifier and Type Interface Description
0 static interface AgentBuilder.CircularityLock 一個循環鎖,阻止ClassFileLocator被提前使用。發生在,一個 transformation邏輯可能會導致另外的類被加載。如果不避免這樣的循環依賴,就會拋出ClassCircularityError錯誤,導致類加載失敗
1 static interface AgentBuilder.ClassFileBufferStrategy 關於class定義的字節緩存buffer如何被使用的策略
2 static class AgentBuilder.Default AgentBuilder的默認實現。默認情況下,byte buddy 無視任何被bootstrap loader加載的類和合成類。1. Self-injection 和 rebase 模式開啓 2. 爲了避免class的格式發生改變,AgentBuilder.disableClassFormatChanges() 3. 所有的類型解析都是 PoolStrategy.Default#FAST,忽略所有的debug信息
3 static interface AgentBuilder.DescriptionStrategy 策略,描述當轉化和定義一個類時,如何解決TypeDescription定義,這個解決指的是尋找+處理
4 static interface AgentBuilder.FallbackStrategy 失敗策略,允許萬一失敗時,可以再次進行transformation或者redefine/retransformation。如果這樣做了,可能就是會使用typepool,而不是一個已經加載的type description–相當於某個class的備份。如果class loader不能找到所有的依賴的類,會拋出異常。使用typepool,由於時懶加載,所以會規避異常,直到被使用
5 static interface AgentBuilder.Identified 用來描述AgentBuilder處理哪幾種mathcer,這個就是提供給mather一個標識,便於篩選
6 static interface AgentBuilder.Ignored 允許聲明,忽略那些具體的方法
7 static interface AgentBuilder.InitializationStrategy 初始化策略,決定LoadedTypeInitializer是如何加載輔助類。agentbuilder不能重用TypeResolutionStrategy策略,是因爲Javaagents不能獲取到一個被transformation過,已經加載的類。所以所以不同的初始化加載策略更有效
8 static interface AgentBuilder.InjectionStrategy 將輔助類注入classloader的策略
9 static interface AgentBuilder.InstallationListener 監聽器,安裝和重置class file transformer事件會喚醒這個監聽器
10 static class AgentBuilder.LambdaInstrumentationStrategy lambda風格的語法特性開啓
11 static interface AgentBuilder.Listener 一個an instrumentation 運行時會觸發一堆事件,這個監聽器是用來接受這些事件的
12 static interface AgentBuilder.LocationStrategy 修改一個類時,描述如何去創建ClassFileLocator的策略
13 static interface AgentBuilder.Matchable<T extends AgentBuilder.Matchable> 繼承了mathcer的一個抽象實現,鏈式的結構
14 static interface AgentBuilder.PoolStrategy 描述AgentBuilder如何尋找和加載類TypeDescription的策略
15 static interface AgentBuilder.RawMatcher 一個matcher,用來匹配類並決定AgentBuilder.TransformerClassFileTransformer運行期間是否被使用
16 static interface AgentBuilder.RedefinitionListenable 允許在redefine的過程中註冊一堆監聽器
17 static class AgentBuilder.RedefinitionStrategy redefinition策略,描述agent如何控制已經被agent 加載到內存裏面的類
18 static interface AgentBuilder.Transformer 應用DynamicType(定義的類修改),將DynamicType,對接到ClassFileTransformer
19 static interface AgentBuilder.TransformerDecorator AgentBuilder.Transformer的裝飾器
20 static interface AgentBuilder.TypeStrategy 描述創建一個要被修改的類型,如何創建的方式

2.2 方法

相當於API的翻譯,會有簡單解釋,覺得不準請看上面的API文檔。

  • 加載過 往往意味值loaded,就是指classloader已經加載過目標類。bytebuddy通常就是感知類的加載,並且返回一個加工過的類。如此完成字節碼修改的作用。
order return type method 描述
0 AgentBuilder with(ByteBuddy byteBuddy) ByteBuddy是用來創建DynamicType的。這裏就是接受並生成一個AgentBuilder
1 AgentBuilder with(Listener listener) 註冊監聽器,創建agent的時候,會喚醒這個Listener。注意的是可以註冊多個,如果早已經註冊,也會重複喚醒,不會只喚醒一個
2 AgentBuilder with(CircularityLock circularityLock) 一個循環鎖,被用於被執行的代碼會加載新的類時,會獲取這個鎖。當鎖被獲取時,任何classfiletransformer都不能transforming 任何類,默認,所有被創建的agent都使用一個CircularityLock,避免造成一個ClassCircularityError。就是避免被執行的代碼加載新類,同時其他agent也在轉化這個類,造成一個循環依賴的一個鎖
3 AgentBuilder with(PoolStrategy poolStrategy) 加載的類時的策略
4 AgentBuilder with(LocationStrategy locationStrategy) 尋找類的位置,利用 class name 加載類二進制數據的策略
5 AgentBuilder with(TypeStrategy typeStrategy) type 應該如何被transformed,eg, rebased或者redefined
6 AgentBuilder with(InitializationStrategy initializationStrategy) 生成一個類時的初始化策略,一個初始化策略是在類被加載後,啓動時生效。初始化行爲,必須在transformation行爲之後,因爲java agent 是在加載一個類之前生效。默認,被注入到對象的初始化邏輯,需要查詢一個全局對象來找到所有需要被注入到目標類型的對象
7 RedefinitionListenable.WithoutBatchStrategy with(RedefinitionStrategy redefinitionStrategy) 明確早已經被修改過且加被加載過類的優先級,目的是來安轉transformer會用到。注意定一個redefinition strategy會重置之前的一些列的策略。重要的是,絕大多數JVM不支持被加載過的類,類結構被重新修改,因此默認打開AgentBuilder#disableClassFormatChanges()
8 AgentBuilder with(LambdaInstrumentationStrategy lambdaInstrumentationStrategy) Lambda表達式特性,忽慮掉
9 AgentBuilder with(DescriptionStrategy descriptionStrategy)) 定義被加載類的,解決(resolving)策略。
10 AgentBuilder (FallbackStrategy fallbackStrategy) 失敗策略,比如轉換失敗時,允許重試一次
11 AgentBuilder with(ClassFileBufferStrategy classFileBufferStrategy) 類定義的buffer如何被使用
12 AgentBuilder with(InstallationListener installationListener) agent安轉時,會有安裝事件,這個時間會喚醒這個監聽器。這個監聽器,只有在classfiletransformer安裝時會被觸發。classfiletransformer的安裝就是agent builder通過創建的ResettableClassFileTransformer執行installation methods 和uninstalled
13 AgentBuilder with(InjectionStrategy injectionStrategy) 輔助類注入到classloader的策略
14 AgentBuilder enableNativeMethodPrefix(String prefix) 給被修改的方法,添加本地方法前綴。注意這個前綴也能給非本地方法用。當Instrumentation安裝agent是,也能用這個前綴來註冊
15 AgentBuilder disableNativeMethodPrefix() 關閉一個本地方法的前綴
16 AgentBuilder disableClassFormatChanges() 禁止所有對class file 的改變,當時用這個策略時,就不存在使用rebase的可能—被修改的方法會完全被替代,而不是被重名。可去搜索rebase和redefine的區別。還有就是在生成類型是,加載初始化動作。這個行爲和設置InitializationStrategy.NoOpTypeStrategy.Default#REDEFINE_FROZEN是等價的。也等同於配置ByteBuddyImplementation.Context.Disabled。使用這個策略是讓bute buddy 穿件一個 凍結的 instrumented類型和排除調所有的配置行爲
17 AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Class<?>… type) module ,jdk9的概念,不用管
18 AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, JavaModule… module)
19 AgentBuilder assureReadEdgeTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules)
20 AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Class<?>… type)
21 AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, JavaModule… module)
22 AgentBuilder assureReadEdgeFromAndTo(Instrumentation instrumentation, Collection<? extends JavaModule> modules) 都是moudle權限控制相關的
23 Identified.Narrowabler type(ElementMatcher<? super TypeDescription> typeMatcher) 每一個類被加載時會觸發一個事件,被agent感知到。這個方法就是用來配一個被加載的類型,目的是應用AgentBuilder.Transformer的修改,在這個類型被加載之前。如果有幾個matcher都命中一個類型,那麼最後一個生效。如果這個matcher是鏈式的,即還有下一個matcher,那麼matcher執行的順序就是註冊的順序,後面matcher對應的transformations會覆蓋前面的。如果不想被覆蓋可以註冊爲terminal最後一個Identified.Extendable#asTerminalTransformation(),這樣就不有有下一個matcher被使用。注意: AgentBuilder#ignore(ElementMatcher)會在mather之前被應用,來排除忽略的類。
24 Identified.Narrowabler type(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher) 唯一的不同是添加了classLoaderMatcher參數,這個classLoader被用來加載目標類的loader
25 Identified.Narrowabler Identified.Narrowable type(ElementMatcher<? super TypeDescription> typeMatcher,ElementMatcher<? super ClassLoader> classLoaderMatcher,ElementMatcher<? super JavaModule> moduleMatcher); moduleMatcher jdk9的語法,只是多了層判斷
26 Identified.Narrowabler type(RawMatcher matcher); RawMatcher 後面詳細分析,文檔只說用來決定Transformer是否被使用
27 Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher); 無視方法的matcher
28 Ignored ignore(ElementMatcher<? super TypeDescription> typeMatcher, ElementMatcher<? super ClassLoader> classLoaderMatcher)
29 Ignored ignore(RawMatcher rawMatcher)
30 ClassFileTransformer makeRaw() ClassFileTransformer是jdk instrument包的類。這個方法是創建一個原生的這樣的類。當時用原生的ClassFileTransformerInstallationListener回調就不會生效,而且RedefinitionStrategy的策略也不會應用到當前加載的類中
31 ResettableClassFileTransformer installOn(Instrumentation instrumentation) 單純使用instrument時,往往是在premain或者agentmain函數裏,執行instrumentation.addTransformer(ClassFileTransformer),installOn 就是將創建一個ResettableClassFileTransformer添加進instrumentation。如果retransformation是打開的,那麼retransformed時讓ClassFileTransformer再次生效。意思是ClassFileTransformer是鏈式的,每個ClassFileTransformer被添加的時候有個canRetransformed屬性。retransformed,就是修改已經加載內存裏面的類,執行時會再次觸發ClassFileTransformer,這時只會觸發canRetransformed爲true的ClassFileTransformer
32 ResettableClassFileTransformer ResettableClassFileTransformer installOn(Instrumentation instrumentation, TransformerDecorator transformerDecorator); installOn時接受額外的Transformer
33 ResettableClassFileTransformer installOnByteBuddyAgent() ByteBuddyAgent是bytebutty對jdk.attachagentmain的一個封裝。根據目標jvm實例的pid,在運行中attach上去,發出加載agent.jar包命令
34 ResettableClassFileTransformer installOnByteBuddyAgent(TransformerDecorator transformerDecorator);

三、詳細介紹

3.1 Matchable

interface Matchable<T extends Matchable<T>>
在這裏插入圖片描述
interface Matchable<T extends Matchable<T>>{}
鏈式的matcher,如果前一個matcher命中,那麼就返回下一個matcher
比如這個and方法,返回一個Matchable

interface Matchable<T extends Matchable<T>> {

        /**
         * Defines a matching that is positive if both the previous matcher and the supplied matcher are matched. When matching a
         * type, class loaders are not considered.
         *
         * @param typeMatcher A matcher for the type being matched.
         * @return A chained matcher.
         */
        T and(ElementMatcher<? super TypeDescription> typeMatcher);

AbstractBase是他的抽象實現

        abstract class AbstractBase<S extends Matchable<S>> implements Matchable<S> {

            /**
             * {@inheritDoc}
             */
            public S and(ElementMatcher<? super TypeDescription> typeMatcher) {
                return and(typeMatcher, any());
            }

3.2 Ignored

interface Ignored extends Matchable<Ignored>, AgentBuilder
過濾接口是鏈式的一個忽略結構,前一個不命中然後下一個
在這裏插入圖片描述

3.3 LocationStrategy

interface LocationStrategy定位一個類的策略
在這裏插入圖片描述

子類 描述
NoOP 找不到類,沒有對象
Compound 多個 ClassFileLocator中尋找類定義
Simple 簡單的定義
ForClassLoadder

3.3.1 ClassFileLocator

用來需找類,並返回類定義byte[]LocationStrategy核心就是如何使用ClassFileLocator這個類。
內部11個實現類
在這裏插入圖片描述

order return type method 描述
0 Resolution locate(String name) 根據類的名稱,返回一個Resolution。Resolutiong包含類的信息,比如isResolved代表是否找到,byte[] resolve()返回類的字節碼定義

3.3.1.1 Resolution

ClassFileLocator的內部接口Resolution代表類的二進制信息
在這裏插入圖片描述

order return type method 描述
0 boolean isResolved() 檢查是否存在這個類
1 byte[] resolve() 找到類的定義,返回一個byte[]
1. Illegal

找不到時
Resolution的實現類
class Illegal implements Resolution。可以看到,獲取類的二進制信息時直接拋出異常

 /**
             * {@inheritDoc}
             */
            public boolean isResolved() {
                return false;
            }

            /**
             * {@inheritDoc}
             */
            public byte[] resolve() {
                throw new IllegalStateException("Could not locate class file for " + typeName);
            }
2. Explicit

找到時
Resolution的實現類
class Explicit implements Resolution。可以看到,獲取類的二進制信息

 /**
      class Explicit implements Resolution {

            /**
             * The represented data.
             */
            private final byte[] binaryRepresentation;

            /**
             * Creates a new explicit resolution of a given array of binary data.
             *
             * @param binaryRepresentation The binary data to represent. The array must not be modified.
             */
            @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is not to be modified by contract")
            public Explicit(byte[] binaryRepresentation) {
                this.binaryRepresentation = binaryRepresentation;
            }

            /**
             * {@inheritDoc}
             */
            public boolean isResolved() {
                return true;
            }

            /**
             * {@inheritDoc}
             */
            @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The array is not to be modified by contract")
            public byte[] resolve() {
                return binaryRepresentation;
            }
        }

3.3.1.2 NoOp

enum NoOp implements ClassFileLocator 找不到對象,返回Illegal Resolution

3.3.1.3 Simple

class Simple implements ClassFileLocator,查找類定的簡單實現
Filed
private final Map<String, byte[]> classFiles;
核心的Map ,一個由類名和 類定義,組成kv的ClassFileLocator
method
只寫幾個,Simple體現在,函數都是接受類定義byte[],放入ClassFileLocator
比如of方法

   public static ClassFileLocator of(Map<TypeDescription, byte[]> binaryRepresentations) {
            Map<String, byte[]> classFiles = new HashMap<String, byte[]>();
            for (Map.Entry<TypeDescription, byte[]> entry : binaryRepresentations.entrySet()) {
                classFiles.put(entry.getKey().getName(), entry.getValue());
            }
            return new Simple(classFiles);
        }

3.3.1.4 ForClassLoader

class ForClassLoader implements ClassFileLocator
需要給定classloader,在classloader中找類
Filed
核心就是這個
private final ClassLoader classLoader;
method
核心方法locate,使用classloader尋找

 protected static Resolution locate(ClassLoader classLoader, String name) throws IOException {
            InputStream inputStream = classLoader.getResourceAsStream(name.replace('.', '/') + CLASS_FILE_EXTENSION);
            if (inputStream != null) {
                try {
                    return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
                } finally {
                    inputStream.close();
                }
            } else {
                return new Resolution.Illegal(name);
            }
        }

3.3.1.5 ForModule

Module jDK9的特性,比classLoader更高一級
class ForModule implements ClassFileLocator

3.3.1.6 ForJarFile

class ForJarFile implements ClassFileLocator
jar包裏面尋找class文件。
可以看到locate方法在jar裏需找類的方法

        public Resolution locate(String name) throws IOException {
            ZipEntry zipEntry = jarFile.getEntry(name.replace('.', '/') + CLASS_FILE_EXTENSION);
            if (zipEntry == null) {
                return new Resolution.Illegal(name);
            } else {
                InputStream inputStream = jarFile.getInputStream(zipEntry);
                try {
                    return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
                } finally {
                    inputStream.close();
                }
            }
        }

3.3.1.6 ForModuleFile、ForFolder、ForUrl、PackageDiscriminating

從 module,Folder,ForURL中尋找class文件
PackageDiscriminating 是包

3.3.1.7 Compound 一堆classFileLocators的接口

支持多個classFileLocatorlocate方法查找時,也是從classFileLocator中遍歷查找優先,返回第一個。

/**
         * The {@link ClassFileLocator}s which are represented by this compound
         * class file locator  in the order of their application.
         */
        private final List<ClassFileLocator> classFileLocators;

        /**
         * Creates a new compound class file locator.
         *
         * @param classFileLocator The {@link ClassFileLocator}s to be
         *                         represented by this compound class file locator in the order of their application.
         */
        public Compound(ClassFileLocator... classFileLocator) {
            this(Arrays.asList(classFileLocator));
        }

        /**
         * Creates a new compound class file locator.
         *
         * @param classFileLocators The {@link ClassFileLocator}s to be represented by this compound class file locator in
         *                          the order of their application.
         */
        public Compound(List<? extends ClassFileLocator> classFileLocators) {
            this.classFileLocators = new ArrayList<ClassFileLocator>();
            for (ClassFileLocator classFileLocator : classFileLocators) {
                if (classFileLocator instanceof Compound) {
                    this.classFileLocators.addAll(((Compound) classFileLocator).classFileLocators);
                } else if (!(classFileLocator instanceof NoOp)) {
                    this.classFileLocators.add(classFileLocator);
                }
            }
        }

        /**
         * {@inheritDoc}
         */
        public Resolution locate(String name) throws IOException {
            for (ClassFileLocator classFileLocator : classFileLocators) {
                Resolution resolution = classFileLocator.locate(name);
                if (resolution.isResolved()) {
                    return resolution;
                }
            }
            return new Resolution.Illegal(name);
        }

3.3.2 NoOp

enum NoOp implements LocationStrategy
找不到類的定義

 enum NoOp implements LocationStrategy {

            /**
             * The singleton instance.
             */
            INSTANCE;

            /**
             * {@inheritDoc}
             */
            public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
                return ClassFileLocator.NoOp.INSTANCE;
            }
        }

3.3.3 ForClassLoader

enum ForClassLoader implements LocationStrategy
從classloader中尋找
Filed

  • STRONG 對目前用來尋找class的classloader,保留一個強reference,好處是classloader不會被GC
  • WEAK 對目前用來尋找class的classloader,保留一個弱的reference,當classloader被GC掉時,classFileLocator就會失效

Method

  • withFallbackTo備份尋找策略,當給定策略找不到是,會使用這個策略。
          public LocationStrategy withFallbackTo(List<? extends LocationStrategy> locationStrategies) {
                List<LocationStrategy> allLocationStrategies = new ArrayList<LocationStrategy>(locationStrategies.size() + 1);
                allLocationStrategies.add(this);
                allLocationStrategies.addAll(locationStrategies);
                return new Compound(allLocationStrategies);
            }

3.3.4 Simple

Simple implements LocationStrategy,直接就是類的賦值,沒有額外處理

    class Simple implements LocationStrategy {

            /**
             * The class file locator to query.
             */
            private final ClassFileLocator classFileLocator;

            /**
             * A simple location strategy that queries a given class file locator.
             *
             * @param classFileLocator The class file locator to query.
             */
            public Simple(ClassFileLocator classFileLocator) {
                this.classFileLocator = classFileLocator;
            }

            /**
             * {@inheritDoc}
             */
            public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
                return classFileLocator;
            }
        }

3.3.4 Compound

就是使用ClassFileLocatorCompound,組合多個ClassFileLocator去定位類,返回第一個命中的。
class Compound implements LocationStrategy

           public ClassFileLocator classFileLocator(ClassLoader classLoader, JavaModule module) {
                List<ClassFileLocator> classFileLocators = new ArrayList<ClassFileLocator>(locationStrategies.size());
                for (LocationStrategy locationStrategy : locationStrategies) {
                    classFileLocators.add(locationStrategy.classFileLocator(classLoader, module));
                }
                return new ClassFileLocator.Compound(classFileLocators);
            }

在這裏插入圖片描述

3.3 Identified

interface Identified

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章