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

