做者:小傅哥
博客:https://bugstack.cn - 彙總系列專題文章
java
沉澱、分享、成長,讓本身和他人都能有所收穫!
相對於小傅哥
以前編寫的字節碼編程; ASM
、Javassist
系列,Byte Buddy
玩法上更加高級,你能夠徹底不須要了解一個類和方法塊是如何經過 指令碼
LDC、LOAD、STORE、IRETURN... 生成出來的。就像它的官網介紹; git
Byte Buddy
是一個代碼生成和操做庫,用於在 Java
應用程序運行時建立和修改 Java
類,而無需編譯器的幫助。除了 Java
類庫附帶的代碼生成實用程序外,Byte Buddy
還容許建立任意類,而且不限於實現用於建立運行時代理的接口。此外,Byte Buddy
提供了一種方便的 API,可使用 Java
代理或在構建過程當中手動更改類。程序員
2015年10月,Byte Buddy被 Oracle 授予了 Duke's Choice大獎。該獎項對Byte Buddy的「 Java技術方面的巨大創新 」表示讚揚。咱們爲得到此獎項感到很是榮幸,並感謝全部幫助Byte Buddy取得成功的用戶以及其餘全部人。咱們真的很感激!
除了這些簡單的介紹外,還能夠經過官網:https://bytebuddy.net
,去了解更多關於 Byte Buddy
的內容。github
好! 那麼接下來,咱們開始從 HelloWorld
開始。深刻了解一個技能前,先多多運行,這樣總歸能讓找到學習的快樂。編程
itstack-demo-bytecode-2-01
,能夠關注公衆號:bugstack蟲洞棧
,回覆源碼下載獲取。你會得到一個下載連接列表,打開后里面的第17個「由於我有好多開源代碼」
,記得給個Star
!每個程序員,都運行過 N
多個 HelloWorld
,就像很熟悉的 Java
;ide
public class Hi { public static void main(String[] args) { System.out.println("Byte-buddy Hi HelloWorld By 小傅哥(bugstack.cn)"); } }
那麼咱們接下來就經過使用動態字節碼生成的方式,來建立出能夠輸出 HelloWorld
的程序。函數
新知識點的學習不要慌,最主要是找到一個能夠入手的點,經過這樣的一個點去慢慢解開整個程序的面紗。性能
在咱們看官網文檔中,從它的介紹了就已經提供了一個很是簡單的例子,用於輸出 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
和執行,以及調用方法 toString()
。也就是最終咱們輸出了想要的結果。那麼,若是你不能看到這樣一段方法塊,把咱們的代碼改造後的樣子,內心仍是有點虛。那麼,咱們經過字節碼輸出到文件,看下具體被改造後的樣子,以下;
編譯後的Class文件,ByteBuddyHelloWorld.class
public class HelloWorld { public String toString() { return "Hello World!"; } public HelloWorld() { } }
在官網來看,這是一個很是簡單而且能體現 Byte buddy
的例子。可是與咱們平時想建立出來的 main
方法相比,仍是有些差別。那麼接下來,咱們嘗試使用字節碼編程技術建立出這樣一個方法。
接下來的例子會經過一點點的增長代碼梳理,不斷的把一個方法完整的建立出來。
爲了能夠更加清晰的看到每一步對字節碼編程後,所建立出來的方法樣子(clazz),咱們須要輸出字節碼生成 clazz
。在Byte buddy中默認提供了一個 dynamicType.saveIn()
方法,咱們暫時先不使用,而是經過字節碼進行保存。
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
基礎的內容,輸出字節碼到文件中。DynamicType.Unloaded<?> dynamicType = new ByteBuddy() .subclass(Object.class) .name("org.itstack.demo.bytebuddy.HelloWorld") .make(); // 輸出類字節碼 outputClazz(dynamicType.getBytes());
此時class文件:
public class HelloWorld { public HelloWorld() { } }
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
。因此能夠執行相加運算,並又能保留原有單元的屬性。
此時class文件:
public class HelloWorld { public static void main(String[] args) { String var10000 = "Hello World!"; } public HelloWorld() { } }
此時基本已經能夠看到咱們日常編寫的 Hello World
影子了,但還能輸出結果。
爲了能讓咱們使用字節碼編程建立的方法去輸出一段 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();
總體來看變化並不大,只有 intercept(MethodDelegation.to(Hi.class))
,使用了一段委託函數,真正去執行輸出的是另外的函數方法。
public
類此時class文件:
public class HelloWorld { public static void main(String[] args) { Hi.main(var0); } public HelloWorld() { } }
Hi.main
是定義出來的委託函數。也就是一個 HelloWorld
爲了可讓整個方法運行起來,咱們須要添加字節碼加載和反射調用的代碼塊,以下;
// 加載類 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
效果圖
Byte buddy
中,須要掌握幾個關鍵信息;建立方法、定義屬性、攔截委託、輸出字節碼,以及最終的運行。這樣的一個簡單過程,能夠很快的瞭解到如何使用 Byte buddy
。Byte buddy
方法經過實際的案例去模擬建設,在這個過程當中增強學習使用。一些基礎知識也能夠經過官方文檔進行學習;https://bytebuddy.net。CodeGuide | 程序員編碼指南 Go!
本代碼庫是做者小傅哥多年從事一線互聯網 Java 開發的學習歷程技術彙總,旨在爲你們提供一個清晰詳細的學習教程,側重點更傾向編寫Java核心內容。若是本倉庫能爲您提供幫助,請給予支持(關注、點贊、分享)!