作者:小傅哥 博客:https://
沉淀、分享、成長,讓自己和他人都能有所收獲!
一、前言
相對于小傅哥
之前編寫的字節(jié)碼編程; ASM
、Javassist
系列,Byte Buddy
玩法上更加高級,你可以完全不需要了解一個類和方法塊是如何通過 指令碼
LDC、LOAD、STORE、IRETURN… 生成出來的。就像它的官網介紹;
Byte Buddy
是一個代碼生成和操作庫,用于在 Java
應用程序運行時創(chuàng)建和修改 Java
類,而無需編譯器的幫助。除了 Java
類庫附帶的代碼生成實用程序外,Byte Buddy
還允許創(chuàng)建任意類,并且不限于實現用于創(chuàng)建運行時代理的接口。此外,Byte Buddy
提供了一種方便的 API,可以使用 Java
代理或在構建過程中手動更改類。
無需理解字節(jié)碼指令,即可使用簡單的 API 就能很容易操作字節(jié)碼,控制類和方法。 已支持Java 11,庫輕量,僅取決于Java字節(jié)代碼解析器庫ASM的訪問者API,它本身不需要任何其他依賴項。 比起JDK動態(tài)代理、cglib、Javassist,Byte Buddy在性能上具有一定的優(yōu)勢。
2015年10月,Byte Buddy被 Oracle 授予了 Duke’s Choice大獎。該獎項對Byte Buddy的“ Java技術方面的巨大創(chuàng)新 ”表示贊賞。我們?yōu)楂@得此獎項感到非常榮幸,并感謝所有幫助Byte Buddy取得成功的用戶以及其他所有人。我們真的很感激!
除了這些簡單的介紹外,還可以通過官網:https://
,去了解更多關于 Byte Buddy
的內容。
好! 那么接下來,我們開始從 HelloWorld
開始。深入了解一個技能前,先多多運行,這樣總歸能讓找到學習的快樂。
二、開發(fā)環(huán)境
JDK 1.8.0 byte-buddy 1.10.9 byte-buddy-agent 1.10.9 本章涉及源碼在:itstack-demo-bytecode-2-01
,可以關注公眾號 :bugstack蟲洞棧
,回復源碼下載獲取。你會獲得一個下載鏈接列表,打開后里面的第17個「因為我有好多開源代碼」
,記得給個Star
!
三、案例目標
每一個程序員,都運行過 N
多個HelloWorld
,就像很熟悉的 Java
;
public class Hi {
public static void main ( String[ ] args) {
System. out. println ( "Byte-buddy Hi HelloWorld By 小傅哥()" ) ;
}
}
那么我們接下來就通過使用動態(tài)字節(jié)碼生成的方式,來創(chuàng)建出可以輸出 HelloWorld
的程序。
新知識點的學習不要慌,最主要是找到一個可以入手的點,通過這樣的一個點去慢慢解開整個程序的面紗。
四、技術實現
1. 官網經典例子
在我們看官網文檔中,從它的介紹了就已經提供了一個非常簡單的例子,用于輸出 HelloWorld
,我們在這展示并講解下。
案例代碼:
String helloWorld = new ByteBuddy ( )
. subclass ( Object. class )
. method ( named ( "toString" ) )
. intercept ( FixedValue. value ( "Hello World!" ) )
. make ( )
. load ( getClass ( ) . getClassLoader ( ) )
. getLoaded ( )
. newInstance ( )
. toString ( ) ;
System. out. println ( helloWorld) ; // Hello World!
他的運行結果就是一行,Hello World!
,整個代碼塊核心功能就是通過 method(named("toString"))
,找到 toString 方法,再通過攔截 intercept
,設定此方法的返回值。FixedValue.value("Hello World!")
。到這里其實一個基本的方法就通過 Byte-buddy
,改造完成。
接下來的這一段主要是用于加載生成后的 Class
和執(zhí)行,以及調用方法 toString()
。也就是最終我們輸出了想要的結果。那么,如果你不能看到這樣一段方法塊,把我們的代碼改造后的樣子,心里還是有點虛。那么,我們通過字節(jié)碼輸出到文件,看下具體被改造后的樣子,如下;
編譯后的Class文件 ,ByteBuddyHelloWorld.class
public class HelloWorld {
public String toString ( ) {
return "Hello World!" ;
}
public HelloWorld ( ) {
}
}
在官網來看,這是一個非常簡單并且能體現 Byte buddy
的例子。但是與我們平時想創(chuàng)建出來的 main
方法相比,還是有些差異。那么接下來,我們嘗試使用字節(jié)碼編程技術創(chuàng)建出這樣一個方法。
2. 字節(jié)碼創(chuàng)建類和方法
接下來的例子會通過一點點的增加代碼梳理,不斷的把一個方法完整的創(chuàng)建出來。
2.1 定義輸出字節(jié)碼方法
為了可以更加清晰的看到每一步對字節(jié)碼編程后,所創(chuàng)建出來的方法樣子(clazz),我們需要輸出字節(jié)碼生成 clazz
。在Byte buddy中默認提供了一個 dynamicType.saveIn()
方法,我們暫時先不使用,而是通過字節(jié)碼進行保存。
private static void outputClazz ( byte [ ] bytes) {
FileOutputStream out = null;
try {
String pathName = ApiTest. class . getResource ( "/" ) . getPath ( ) + "ByteBuddyHelloWorld.class" ;
out = new FileOutputStream ( new File ( pathName) ) ;
System. out. println ( "類輸出路徑:" + pathName) ;
out. write ( bytes) ;
} catch ( Exception e) {
e. printStackTrace ( ) ;
} finally {
if ( null != out) try {
out. close ( ) ;
} catch ( IOException e) {
e. printStackTrace ( ) ;
}
}
}
這個方法我們在之前也用到過,主要就是一個 Java
基礎的內容,輸出字節(jié)碼到文件中。
2.2 創(chuàng)建類信息
DynamicType. Unloaded< ? > dynamicType = new ByteBuddy ( )
. subclass ( Object. class )
. name ( "org.itstack.demo.bytebuddy.HelloWorld" )
. make ( ) ;
// 輸出類字節(jié)碼
outputClazz ( dynamicType. getBytes ( ) ) ;
創(chuàng)建類和定義類名,如果不寫類名會自動生成要給類名。
此時class文件:
public class HelloWorld {
public HelloWorld ( ) {
}
}
2.3 創(chuàng)建main方法
DynamicType. Unloaded< ? > dynamicType = new ByteBuddy ( )
. subclass ( Object. class )
. name ( "org.itstack.demo.bytebuddy.HelloWorld" )
. defineMethod ( "main" , void . class , Modifier. PUBLIC + Modifier. STATIC)
. withParameter ( String[ ] . class , "args" )
. intercept ( FixedValue. value ( "Hello World!" ) )
. make ( ) ;
與上面相比新增的代碼片段;
defineMethod("main", void.class, Modifier.PUBLIC + Modifier.STATIC)
,定義方法;名稱、返回類型、屬性public static withParameter(String[].class, "args")
,定義參數;參數類型、參數名稱intercept(FixedValue.value("Hello World!"))
,攔截設置返回值,但此時還能滿足我們的要求。
這里有一個知識點,Modifier.PUBLIC + Modifier.STATIC
,這是一個是二進制相加,每一個類型都在二進制中占有一位。例如 1 2 4 8 ...
對應的二進制占位 1111
。所以可以執(zhí)行相加運算,并又能保留原有單元的屬性。
此時class文件:
public class HelloWorld {
public static void main ( String[ ] args) {
String var10000 = "Hello World!" ;
}
public HelloWorld ( ) {
}
}
此時基本已經可以看到我們平常編寫的 Hello World
影子了,但還能輸出結果。
2.4 委托函數使用
為了能讓我們使用字節(jié)碼編程創(chuàng)建的方法去輸出一段 Hello World
,那么這里需要使用到委托
。
DynamicType. Unloaded< ? > dynamicType = new ByteBuddy ( )
. subclass ( Object. class )
. name ( "org.itstack.demo.bytebuddy.HelloWorld" )
. defineMethod ( "main" , void . class , Modifier. PUBLIC + Modifier. STATIC)
. withParameter ( String[ ] . class , "args" )
. intercept ( MethodDelegation. to ( Hi. class ) )
. make ( ) ;
此時class文件:
public class HelloWorld {
public static void main ( String[ ] args) {
Hi. main ( var0) ;
}
public HelloWorld ( ) {
}
}
那么此時就可以輸出我們需要的內容了,Hi.main
是定義出來的委托函數。也就是一個 HelloWorld
五、測試結果
為了可以讓整個方法運行起來,我們需要添加字節(jié)碼加載和反射調用的代碼塊,如下;
// 加載類
Class< ? > clazz = dynamicType. load ( GenerateClazzMethod. class . getClassLoader ( ) )
. getLoaded ( ) ;
// 反射調用
clazz. getMethod ( "main" , String[ ] . class ) . invoke ( clazz. newInstance ( ) , ( Object) new String [ 1 ] ) ;
運行結果
類輸出路徑:/ User/ xiaofuge/ itstack/ git/ github. com/ itstack- demo- bytecode/ itstack- demo- bytecode- 2 - 01 / target/ test- classes/ ByteBuddyHelloWorld. class
helloWorld
Process finished with exit code 0
效果圖
六、總結
在本章節(jié) Byte buddy
中,需要掌握幾個關鍵信息;創(chuàng)建方法、定義屬性、攔截委托、輸出字節(jié)碼,以及最終的運行。這樣的一個簡單過程,可以很快的了解到如何使用 Byte buddy
。 本系列文章后續(xù)會繼續(xù)更新,把常用的 Byte buddy
方法通過實際的案例去模擬建設,在這個過程中加強學習使用。一些基礎知識也可以通過官方文檔進行學習;https:// 。 在學習整理的過程中發(fā)現,關于字節(jié)碼編程方面的資料并不是很全,主要源于大家平時的開發(fā)中基本是用不到的,誰也不可能總去修改字節(jié)碼。但對于補全這樣的成體系完善技術棧資料,卻可以幫助很多需要的人。因此我也會持續(xù)輸出類似這樣空白的技術文章。
七、彩蛋
CodeGuide | 程序員編碼指南 Go!
本代碼庫是作者小傅哥多年從事一線互聯網 Java 開發(fā)的學習歷程技術匯總,旨在為大家提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心內容。如果本倉庫能為您提供幫助,請給予支持(關注、點贊、分享)!