Java字節(jié)碼ByteBuddy使用及原理解析上
什么是ByteBuddy
ByteBuddy
是一個(gè)java的運(yùn)行時(shí)代碼生成庫,他可以幫助你以字節(jié)碼的方式動(dòng)態(tài)修改java類的代碼。
為什么需要ByteBuddy
Java是一個(gè)強(qiáng)類型語言,有著極為嚴(yán)格的類型系統(tǒng)。這個(gè)嚴(yán)格的類型系統(tǒng)可以幫助構(gòu)建嚴(yán)謹(jǐn),更不容易被腐化的代碼,但是也在某些方面限制了java的應(yīng)用。不過為了解決這個(gè)問題,java提供了一套反射的api來幫助使用者感知和修改類的內(nèi)部。
不過反射也有他的缺點(diǎn):
- 反射顯而易見的缺點(diǎn)是慢。我們?cè)谑褂梅瓷渲岸夹枰?jǐn)慎的考慮他對(duì)于當(dāng)前性能的影響,唯有進(jìn)過詳細(xì)的評(píng)估,才能夠放心的使用。
- 反射能夠繞過類型安全檢查。我們?cè)谑褂梅瓷涞臅r(shí)候需要確保相應(yīng)的接口不會(huì)暴露給外部用戶,不然可能造成不小的安全隱患。
而ByteBuddy
就可以幫助我們做到反射能做的事情,而不必受困于他的這些缺點(diǎn)。
ByteBuddy使用
創(chuàng)建一個(gè)類
new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World!")) .make() .saveIn(new File("result"));
上述代碼創(chuàng)建了一個(gè)Object
的子類并且創(chuàng)建了toString
方法輸出Hello World!
通過找到保存的輸出類我們可以看到最后的類是這樣的:
package net.bytebuddy.renamed.java.lang; public class Object$ByteBuddy$tPSTnhZh { public String toString() { return "Hello World!"; } public Object$ByteBuddy$tPSTnhZh() { } }
可以看到我們雖然創(chuàng)建了一個(gè)類,但是我們沒有為這個(gè)類取名,通過結(jié)果得知最后的類名是net.bytebuddy.renamed.java.lang.Object$ByteBuddy$tPSTnhZh
,那么這個(gè)類名是怎么來的呢?
在ByteBuddy中如果沒有指定類名,他會(huì)調(diào)用默認(rèn)的NamingStrategy
策略來生成類名,一般情況下為
父類的全限定名 + $ByteBuddy$ + 隨機(jī)字符串
例如: org.example.MyTest$ByteBuddy$NsT9pB6w
如果父類是java.lang目錄下的類,例如Object,那么會(huì)變成
net.bytebuddy.renamed. + 父類的全限定名 + $ByteBuddy$ + 隨機(jī)字符串
例如: net.bytebuddy.renamed.java.lang.Object$ByteBuddy$2VOeD4Lh
以此來規(guī)避java安全模型的限制。
類型重定義與變基
定義一個(gè)類
package org.example.bytebuddy.test; public class MyClassTest { public String test() { return "my test"; } }
用這個(gè)類來驗(yàn)證如下的能力
類型重定義(type redefinition)
ByteBuddy支持對(duì)于已存在的類進(jìn)行重定義,即可以添加或者刪除類的方法。只不過當(dāng)類的方法被重定義之后,那么原先的方法中的信息就會(huì)丟失。
Class<?> dynamicType = new ByteBuddy() .redefine(MyClassTest.class) .method(ElementMatchers.named("test")) .intercept(FixedValue.value("Hello World!")) .make() .load(String.class.getClassLoader()).getLoaded();
redefine結(jié)果是
類型變基(type rebasing)
rebase操作和redefinition操作最大的區(qū)別就是rebase操作不會(huì)丟失原先的類的方法信息。大致的實(shí)現(xiàn)原理是在變基操作的時(shí)候把所有的方法實(shí)現(xiàn)復(fù)制到重新命名的私有方法(具有和原先方法兼容的簽名)中,這樣原先的方法就不會(huì)丟失。
Class<?> dynamicType = new ByteBuddy() .rebase(MyClassTest.class) .method(ElementMatchers.named("test")) .intercept(FixedValue.value("Hello World!")) .make() .load(String.class.getClassLoader()).getLoaded();
rebase之后結(jié)果
可以看到原先的方法被重命名后保留了下來,并且變成了私有方法。
注意redefinition和rebasing不能修改已經(jīng)被jvm加載的類,不然會(huì)報(bào)錯(cuò)Class already loaded
類的加載
生成了之后為了在代碼中使用,必須要經(jīng)過load
流程。細(xì)心的讀者可能已經(jīng)發(fā)現(xiàn)了上文中已經(jīng)使用到了load
相關(guān)的方法。
構(gòu)建了具體的動(dòng)態(tài)類之后,可以選擇使用saveIn將其結(jié)構(gòu)體存儲(chǔ)下來,也可以選擇將它裝載到虛擬機(jī)中。在類加載器的選擇中,ByteBuddy提供了幾種選擇放在ClassLoadingStrategy.Default
中:
WRAPPER
:這個(gè)策略會(huì)創(chuàng)建一個(gè)新的ByteArrayClassLoader
,并使用傳入的類加載器為父類。WRAPPER_PERSISTENT
:該策略和WRAPPER
大致一致,只是會(huì)將所有的類文件持久化到類加載器中CHILD_FIRST
:這個(gè)策略是WRAPPER
的改版,其中動(dòng)態(tài)類型的優(yōu)先級(jí)會(huì)比父類加載器中的同名類高,即在此種情況下不再是類加載器通常的父類優(yōu)先,而是“子類優(yōu)先”CHILD_FIRST_PERSISTENT
:該策略和CHILD_FIRST
大致一致,只是會(huì)將所有的類文件持久化到類加載器中INJECTION
:這個(gè)策略最為特殊,他不會(huì)創(chuàng)建類加載器,而是通過反射的手段將類注入到指定的類加載器之中。這么做的好處是用這種方法注入的類對(duì)于類加載器中的其他類具有私有權(quán)限,而其他的策略不具備這種能力。
類的重載
前面提到過,rebase和redefine通常沒辦法重新加載已經(jīng)存在的類,但是由于jvm的熱替換(HotSwap)機(jī)制的存在,使得ByteBuddy
可以在加載后也能夠重新定義類。
class Foo { String m() { return "foo"; } } class Bar { String m() { return "bar"; } }
我們通過ByteBuddy的ClassRelodingsTrategy
即可完成熱替換。
ByteBuddyAgent.install(); Foo foo = new Foo(); new ByteBuddy() .redefine(Bar.class) .name(Foo.class.getName()) .make() .load(Foo.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
需要注意的是熱替換機(jī)制必須依賴Java Agent才能使用。Java Agent是一種可以在java項(xiàng)目運(yùn)行前或者運(yùn)行時(shí)動(dòng)態(tài)修改類的技術(shù)。通??梢允褂?javaagent參數(shù)引入java agent。
處理尚未加載的類
ByteBuddy除了可以處理已經(jīng)加載完的類,他也具備處理尚未被加載的類的能力。
ByteBuddy對(duì)java的反射api做了抽象,例如Class
實(shí)例就被表示成了TypeDescription
實(shí)例。事實(shí)上,ByteBuddy只知道如何通過實(shí)現(xiàn)TypeDescription
接口的適配器來處理提供的 Class
。這種抽象的一大優(yōu)勢(shì)是類信息不需要由類加載器提供,可以由任何其他來源提供。
ByteBuddy中可以通過TypePool
獲取類的TypeDescription
,ByteBuddy提供了TypePool
的默認(rèn)實(shí)現(xiàn)TypePool.Default
。這個(gè)類可以幫助我們把java字節(jié)碼轉(zhuǎn)換成TypeDescription
。
Java的類加載器只會(huì)在類第一次使用的時(shí)候加載一次,因此我們可以在java中以如下方式安全的創(chuàng)建一個(gè)類:
package foo; class Bar { }
但是通過如下的方法,我們可以在Bar
這個(gè)類沒有被加載前就提前生成我們自己的Bar
,因此后續(xù)jvm就只會(huì)使用到我們的Bar
參考文章
[1] https://bytebuddy.net/#/tutorial
以上就是Java字節(jié)碼ByteBuddy使用及原理解析上的詳細(xì)內(nèi)容,更多關(guān)于Java字節(jié)碼ByteBuddy的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springmvc—handlermapping三種映射方式
這篇文章主要介紹了springmvc—handlermapping三種映射方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09解決idea找不到類could not find artifact問題
本文總結(jié)了解決Java項(xiàng)目中找不到類的問題的常見解決方案,包括刷新Maven項(xiàng)目、清理IDEA緩存、Maven Clean Install、重新Package、解決依賴沖突和手動(dòng)導(dǎo)入依賴包等方法2025-01-01一文徹底吃透SpringMVC中的轉(zhuǎn)發(fā)和重定向
大家應(yīng)該都知道springmvc本來就會(huì)把返回的字符串作為視圖名解析,然后轉(zhuǎn)發(fā)到對(duì)應(yīng)的視圖,這篇文章主要給大家介紹了關(guān)于SpringMVC中轉(zhuǎn)發(fā)和重定向的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04Mybatis實(shí)現(xiàn)分包定義數(shù)據(jù)庫的原理與過程
這篇文章主要給大家介紹了關(guān)于Mybatis實(shí)現(xiàn)分包定義數(shù)據(jù)庫的原理與過程,文中通過實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-01-01Java Calendar類常用示例_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
從JDK1.1版本開始,在處理日期和時(shí)間時(shí),系統(tǒng)推薦使用Calendar類進(jìn)行實(shí)現(xiàn)。接下來通過實(shí)例代碼給大家詳細(xì)介紹Java Calendar類相關(guān)知識(shí),需要的朋友參考下吧2017-04-04