Java字節(jié)碼ByteBuddy使用及原理解析下
構(gòu)建Java Agent
在應(yīng)用程序中很多時(shí)候都不方便直接修改代碼,java agent模式可以不用直接修改應(yīng)用的代碼就能夠?qū)崿F(xiàn)自己的功能。使用ByteBuddy可以讓我們很容易構(gòu)建自己的agent。事實(shí)上很多的開源Agent都是借助的ByteBuddy來實(shí)現(xiàn)的Agent。關(guān)于java agent我后續(xù)會寫一些文章來進(jìn)一步深入介紹相關(guān)內(nèi)容,在此就不多贅述了。
處理泛型
Java的泛型會在運(yùn)行時(shí)進(jìn)行類型擦除。但是,由于泛型類型可能被嵌入到任何Java類文件中,并由 Java反射API對外暴露。所以將通用信息包含到生成的類中是有意義的。
由此種種,在子類化類、實(shí)現(xiàn)接口或聲明字段或方法時(shí),ByteBuddy接受Type的參數(shù)而不是擦除的Class。也可以使用 TypeDescription.Generic.Builder 明確定義泛型類型。
字段和方法
上述的章節(jié)講述了類的創(chuàng)建與修改,接下來就要講講字段和方法的處理了。其實(shí)在上文中我們也已經(jīng)舉過了相關(guān)的例子了。我們引用了一個(gè)將類的方法替換成其他返回值的例子:
new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.saveIn(new File("result"));現(xiàn)在我們仔細(xì)的審視上述的代碼,在method方法中使用到了ElementMatchers.named方法,這個(gè)方法是ElementMatchers中定義的一系列方法的其中一種,這個(gè)類主要用于創(chuàng)建易于人類閱讀的類和方法匹配機(jī)制。其中定義了大量的方法來助于定義類和方法。
例如:
named("toString").and(returns(String.class)).and(takesArguments(0))上述代碼就是描述的名稱為toString,返回值為String且沒有參數(shù)的方法
接下來來看一個(gè)復(fù)雜的案例:
class Foo {
public String bar() { return null; }
public String foo() { return null; }
public String foo(Object o) { return null; }
}
Foo dynamicFoo = new ByteBuddy()
.subclass(Foo.class)
.method(isDeclaredBy(Foo.class)).intercept(FixedValue.value("One!"))
.method(named("foo")).intercept(FixedValue.value("Two!"))
.method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!"))
.make()
.load(getClass().getClassLoader())
.getLoaded()
.newInstance();在這個(gè)例子中定義了三個(gè)方法匹配,依據(jù)ByteBuddy的實(shí)現(xiàn)原則,上述的調(diào)用是基于堆棧的形式,因此在最后的.method(named("foo").and(takesArguments(1))).intercept(FixedValue.value("Three!"))反而會被最先匹配,當(dāng)他未匹配成功時(shí)會以堆棧的順序依次匹配。
如果不想覆蓋方法,想要重新定義自己的方法,可以使用defineMethod,當(dāng)然這也符合上述的堆棧的執(zhí)行順序。
深入了解FixedValue
在上文中我們已經(jīng)有了一些使用FixedValue的例子了。顧名思義,FixedValue的作用是返回一個(gè)固定的提供的對象。
類會議如下兩種方式記錄這個(gè)對象:
- 固定值寫入類的常量池。常量池主要記住類的屬性,比如類名或者方法名。除了這些反射屬性之外,常量池還有空間來存儲在類的方法或字段中使用的任何字符串或原始值。除了字符串和原始值,類池還可以存儲對其他類型的引用。
- 該值存儲在類的靜態(tài)字段中。所以一旦將類加載到Java虛擬機(jī)中,就必須為該字段分配給定值。
當(dāng)你使用FixedValue.value(Object)時(shí),ByteBuddy會分析參數(shù)的類型,并且存儲下來(優(yōu)先嘗試第一種方法,不可行才會使用第二種方法)。但是請注意,如果值存儲在類池中,則所選方法返回的實(shí)例可能具有不同的對象標(biāo)識。這種時(shí)候就可以使用FixedValue.reference(Object)來始終將對象存儲在靜態(tài)字段中。
委托方法調(diào)用
在很多場景下使用FixedValue返回固定值顯然是遠(yuǎn)遠(yuǎn)不夠的。所以ByteBuddy提供了MethodDelegation來支持更加強(qiáng)大的和自由的方法定義。
看這個(gè)例子:
class Source {
public String hello(String name) { return null; }
}
class Target {
public static String hello(String name) {
return "Hello " + name + "!";
}
}
String helloWorld = new ByteBuddy()
.subclass(Source.class)
.method(named("hello")).intercept(MethodDelegation.to(Target.class))
.make()
.load(ClassLoader.getSystemClassLoader())
.getLoaded()
.newInstance()
.hello("World");
System.out.println(helloWorld);在這個(gè)例子里面我們把Source的hello方法委托給了Target,因此程序輸出了Hello World!而不是null
為了實(shí)現(xiàn)上述的效果MethodDelegation會找到Target的所有可以調(diào)用的方法并且進(jìn)行最佳匹配。在上述方法中因?yàn)橹挥幸粋€(gè)方法,因此匹配非常簡單,那么遇到復(fù)雜的情況MethodDelegation會怎么進(jìn)行匹配呢?我們看下一個(gè)例子:
class Target {
public static String intercept(String name) { return "Hello " + name + "!"; }
public static String intercept(int i) { return Integer.toString(i); }
public static String intercept(Object o) { return o.toString(); }
}這個(gè)例子中的Target有三個(gè)重載方法,我們用這個(gè)類來進(jìn)行測試。經(jīng)過測試,最后輸出的結(jié)果是Hello World!,可能有人會疑惑為什么連方法名都完全不一樣,這也能被委托嗎?
這里就涉及到了ByteBuddy的實(shí)現(xiàn)了。ByteBuddy不要求目標(biāo)方法和源方法同名,回看上述方法,顯然最后綁定的是第一個(gè)intercept方法,這是為什么呢?首先,第二個(gè)方法入?yún)閕nt顯然無法匹配,但是第一和第三個(gè)方法應(yīng)該如何選擇,這就又涉及到了內(nèi)部的實(shí)現(xiàn)問題。ByteBuddy模仿了java編譯器綁定重載方法的實(shí)現(xiàn)方式,總是選擇“最具體”的類型來進(jìn)行綁定。而String顯然比Object更為具體,因此綁定到了第一個(gè)intercept方法。
MethodDelegation可以配合注解@Argument一起使用,@Argument可以通過配置參數(shù)的位置(排在第n個(gè))來進(jìn)行參數(shù)的綁定。實(shí)際上如果你沒有配置此注解,ByteBuddy也會按照注解綁定的方式來處理,例如:
void foo(Object o1, Object o2)
如果原始方法是這樣的,那么ByteBuddy會進(jìn)行如下的解析:
void foo(@Argument(0) Object o1, @Argument(1) Object o2)
第一個(gè)參數(shù)和第二個(gè)參數(shù)會被分配到對應(yīng)的攔截器,如果被攔截的方法少于兩個(gè)參數(shù),或者參數(shù)類型不能匹配,那么就舍棄攔截方法。
MethodDelegation還可以配合很多的注解來處理不同的場景:
@AllArguments:此配置為數(shù)組類型,包含所有源方法的參數(shù)。為此,所有源方法參數(shù)都必須是可分配給數(shù)組的類型。如果不是此方法在匹配時(shí)會被舍棄。@This:這個(gè)注解可以用于獲取當(dāng)前實(shí)例
@Origin:此注解用于獲取方法的簽名,例如:
public static String intercept(@Origin String method) { return "Hello " + method + "!"; }這段代碼會輸出Hello public java.lang.String org.example.bytebuddy.test.Source.hello(java.lang.String)!
訪問成員變量
使用FieldAccessor可以訪問類成員變量,并且可以讀寫變量的值。
我們可以通過FieldAccessor.ofBeanProperty()來為類構(gòu)建Java Bean規(guī)范的get和set方法:
new ByteBuddy()
.subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name("org.example.bytebuddy.FieldTest")
.defineField("myField", String.class, Visibility.PRIVATE)
.defineField("myTest", String.class, Visibility.PRIVATE)
.defineMethod("getMyField", String.class)
.intercept(FieldAccessor.ofBeanProperty())
.make()
.saveIn(new File("result"));
當(dāng)然如果需要自行定義field的綁定名稱,可以通過FieldAccessor.ofField來指定:
new ByteBuddy()
.subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name("org.example.bytebuddy.FieldTest")
.defineField("myField", String.class, Visibility.PRIVATE)
.defineField("myTest", String.class, Visibility.PRIVATE)
.defineMethod("getMyField", String.class)
.intercept(FieldAccessor.ofField("myTest"))
.make()
.saveIn(new File("result"));
總結(jié)
ByteBuddy作為一種高性能的字節(jié)碼組件有著較為廣泛的使用。他的能力非常強(qiáng)大,此處只是介紹了他的部分能力,如果有需要的話可以前往byte-buddy了解更多信息。
以上就是Java字節(jié)碼ByteBuddy使用及原理解析下的詳細(xì)內(nèi)容,更多關(guān)于Java字節(jié)碼ByteBuddy的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java反射_改變private中的變量及方法的簡單實(shí)例
下面小編就為大家?guī)硪黄猨ava反射_改變private中的變量及方法的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06
基于java實(shí)現(xiàn)顏色拾色器并打包成exe
這篇文章主要為大家詳細(xì)介紹了如何基于java編寫一個(gè)簡單的顏色拾色器并打包成exe文件,文中的示例代碼講解詳細(xì),需要的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-10-10
Java語言實(shí)現(xiàn)簡單FTP軟件 FTP上傳下載管理模塊實(shí)現(xiàn)(11)
這篇文章主要為大家詳細(xì)介紹了Java語言實(shí)現(xiàn)簡單FTP軟件,F(xiàn)TP本地文件管理模塊的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Java開發(fā)深入分析講解二叉樹的遞歸和非遞歸遍歷方法
樹是一種重要的非線性數(shù)據(jù)結(jié)構(gòu),直觀地看,它是數(shù)據(jù)元素(在樹中稱為結(jié)點(diǎn))按分支關(guān)系組織起來的結(jié)構(gòu),很象自然界中的樹那樣。樹結(jié)構(gòu)在客觀世界中廣泛存在,如人類社會的族譜和各種社會組織機(jī)構(gòu)都可用樹形象表示,本篇介紹二叉樹的遞歸與非遞歸遍歷的方法2022-05-05
Java核心編程之文件隨機(jī)讀寫類RandomAccessFile詳解
這篇文章主要為大家詳細(xì)介紹了Java核心編程之文件隨機(jī)讀寫類RandomAccessFile,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
apollo更改配置刷新@ConfigurationProperties配置類
這篇文章主要為大家介紹了apollo更改配置刷新@ConfigurationProperties配置類示例解析,apollo更改配置刷新@ConfigurationProperties配置類2023-04-04

