Agentbuilder入門+API詳解
- 一、使用範式:創建一個agent
- 二、AgentBuilder 類結構
- 三、詳細介紹
- 3.1 Matchable
- 3.2 Ignored
- 3.3 LocationStrategy
- 3.3.1 ClassFileLocator
- 3.3.1.1 Resolution
- 3.3.1.2 NoOp
- 3.3.1.3 Simple
- 3.3.1.4 ForClassLoader
- 3.3.1.5 ForModule
- 3.3.1.6 ForJarFile
- 3.3.1.6 ForModuleFile、ForFolder、ForUrl、PackageDiscriminating
- 3.3.1.7 Compound 一堆classFileLocators的接口
- 3.3.2 NoOp
- 3.3.3 ForClassLoader
- 3.3.4 Simple
- 3.3.4 Compound
- 3.3 Identified
使用中發現源碼中的高階用法根本看不懂,狠下心來試着結合文檔和源碼看下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 類結構
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.Transformer 在ClassFileTransformer 運行期間是否被使用 |
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.NoOp 和TypeStrategy.Default#REDEFINE_FROZEN 是等價的。也等同於配置ByteBuddy 爲Implementation.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包的類。這個方法是創建一個原生的這樣的類。當時用原生的ClassFileTransformer ,InstallationListener 回調就不會生效,而且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.attach 和agentmain 的一個封裝。根據目標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的接口
支持多個classFileLocator
。 locate
方法查找時,也是從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
就是使用ClassFileLocator
的Compound
,組合多個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