摘要:動態(tài)編程使用場景通過配置生成代碼,減少重復(fù)編碼,降低維護(hù)成本。動態(tài)生成字節(jié)碼操作字節(jié)碼的工具有,其中有兩個比較流行的,一個是,一個是。
什么是動態(tài)編程作者簡介
傳恒,一個喜歡攝影和旅游的軟件工程師,先后從事餓了么物流蜂鳥自配送和蜂鳥眾包的開發(fā),現(xiàn)在轉(zhuǎn)戰(zhàn) Java,目前負(fù)責(zé)物流策略組分流相關(guān)業(yè)務(wù)的開發(fā)。
動態(tài)編程是相對于靜態(tài)編程而言的,平時我們討論比較多的靜態(tài)編程語言例如Java, 與動態(tài)編程語言例如JavaScript相比,二者有什么明顯的區(qū)別呢? 簡單的說就是在靜態(tài)編程中,類型檢查是在編譯時完成的,而動態(tài)編程中類型檢查是在運行時完成的, 所謂動態(tài)編程就是繞過編譯過程在運行時進(jìn)行操作的技術(shù)。
動態(tài)編程使用場景通過配置生成代碼,減少重復(fù)編碼,降低維護(hù)成本。
AOP的一種實現(xiàn)方式,方便實現(xiàn)性能監(jiān)控和分析,日志,事務(wù),權(quán)限校驗等。
實現(xiàn)新語言的語義,例如Groovy使用ASM生成字節(jié)碼。
單元測試中動態(tài)mock測試依賴。
在Java中有如下幾種方式實現(xiàn)動態(tài)編程:反射
我們常用到的動態(tài)特性主要是反射,在運行時查找對象的屬性和方法,修改作用域,通過方法名稱調(diào)用方法等。在線的應(yīng)用不建議頻繁使用反射,因為反射的性能開銷較大。
動態(tài)代理在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,通過這個類和這個接口可以生成JDK動態(tài)代理類和動態(tài)代理對象。
動態(tài)編譯動態(tài)編譯是從Java 6開始支持的,主要是通過一個JavaCompiler接口來完成的。通過這種方式我們可以直接編譯一個已經(jīng)存在的java文件,也可以在內(nèi)存中動態(tài)生成Java代碼,動態(tài)編譯執(zhí)行。
調(diào)用Java Script引擎Java 6加入了對Script(JSR223)的支持。這是一個腳本框架,提供了讓腳本語言來訪問Java內(nèi)部的方法。你可以在運行的時候找到腳本引擎,然后調(diào)用這個引擎去執(zhí)行腳本,這個腳本API允許你為腳本語言提供Java支持。
動態(tài)生成字節(jié)碼操作java字節(jié)碼的工具有BECL/ASM/CGLIB/Javassist,其中有兩個比較流行的,一個是ASM,一個是Javassist。 ASM直接操作字節(jié)碼指令,執(zhí)行效率高,要求使用者掌握J(rèn)ava類字節(jié)碼文件格式及指令,對使用者的要求比較高。 Javassist提供了更高級的API,執(zhí)行效率相對較差,但無需掌握字節(jié)碼指令的知識,對使用者要求較低,所以接下來我們重點講講Javassist。
Javassist
Javassist(Java Programming Assistant) 使對Java字節(jié)碼的操作變得簡單,它使Java程序能夠在運行時定義新類,并且可以在JVM加載時修改類文件。 與其它類似的字節(jié)碼編輯器不同,它提供兩個級別的API:源級別和字節(jié)碼級別。 如果用戶使用源級別API,他們可以在不知道Java字節(jié)碼規(guī)范的情況下編輯類文件。整個API僅使用Java語言的詞匯表進(jìn)行設(shè)計,你甚至可以使用Java源代碼的方式插入字節(jié)碼。 另外,用戶也可以使用字節(jié)碼級別的API去直接編輯類文件。
// ClassPool 是 CtClass 對象的容器,存儲著CtClass的Hash表。它按需讀取類文件來構(gòu)造CtClass對象,并且保存CtClass對象以便之后使用
ClassPool classPool = ClassPool.getDefault();
// CtClass 表示一個class文件,一個 GtClass(compile-time class)對象用來處理一個class文件,下面是從classpath中查找該類
CtClass ctClass = classPool.get("test.config.ConfigHandle");
// 通知編輯器去尋找對應(yīng)的包
classPool.importPackage("org.mockito.Mockito");
classPool.importPackage("test.adapter.ext.IDowngrade");
classPool.importPackage("test.utils.property.IProperties");
// 使用removeField() removeMethod() 去刪除對應(yīng)的屬性和方法
ctClass.removeField(ctClass.getDeclaredField("serviceHandle"));
ctClass.removeField(ctClass.getDeclaredField("switchHandle"));
ctClass.removeField(ctClass.getDeclaredField("configHandle"));
// CtMethod 和 CtConstructor 提供了 setBody() 方法去修改方法體
CtConstructor ctConstructor = ctClass.getDeclaredConstructors()[0];
ctConstructor.setBody("{this.mySwitch = Mockito.mock(IDowngrade.class);
" +
" this.myConfig = Mockito.mock(IProperties.class);}");
// toClass() 請求當(dāng)前線程的 ClassLoader 去加載 CtClass 所代表的類文件
ctClass.toClass();
//輸出成二進(jìn)制格式
//byte[] b = ctClass.toBytecode();
//輸出class文件到目錄中
//ctClass.writeFile("/tmp");
ClassPool是CtClass對象的容器,因為編譯器在編譯引用CtClass代表的Java類的源代碼時,可能會引用CtClass對象,所以一旦一個CtClass被創(chuàng)建,它就被保存在ClassPool中。
如果事先知道要修改哪些類,修改類的最簡單方法如下:
調(diào)用 ClassPool.get() 獲取 CtClass 對象
修改對象
調(diào)用 CtClass 對象的 writeFile() 或者 toBytecode() 獲得修改過的類文件。
如果需要定義一個新類,只需要
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("HelloWorld");
凍結(jié)classes
如果一個 CtClass 對象通過 writeFile(), toClass(), toBytecode()被轉(zhuǎn)換成一個類文件,該CtClass對象會被凍結(jié)起來,不允許再修改,因為一個類只能被JVM加載一次。
CtClasss cc = ...;
:
cc.writeFile();
cc.defrost();
cc.setSuperclass(...); // 類已經(jīng)被解凍
Class 搜索路徑:
通過 ClassPool.getDefault() 獲取的ClassPool默認(rèn)使用JVM的類搜索路徑。如果程序運行在JBoss或者Tomcat等Web服務(wù)器上,ClassPool可能無法找到用戶自己定義的類,因為這種Web服務(wù)器使用多個類加載器作為系統(tǒng)類加載器。在這種情況下,ClassPool必須添加額外的類搜索路徑。
pool.insertClassPath(new ClassClassPath(this.getClass())); // 當(dāng)前的類使用的類路徑,注冊到類搜索路徑
pool.insertClassPath("/usr/local/javalib"); // 添加目錄 /usr/local/javalib 到類搜索路徑
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp); // 注冊URL到搜索路徑
在Java中,多個類加載器是可以共存的。每個類加載器創(chuàng)建了自己的命名空間,不同的類加載器可以加載具有相同類名的不同類文件,被加載的類也會被視為不同的類。此功能使我們能夠在單個JVM上面運行多個應(yīng)用程序,即使這些程序包含具有相同名稱的類。
注意,JVM不允許動態(tài)重新加載類,一旦類加載器加載了一個類,就不能再在運行時重新加載該類的其它版本。因此,在JVM加載類之后,就不能再更改該類的定義。 但是,JPDA(Java平臺調(diào)試器架構(gòu))提供有限的重新加載類的能力,如果相同的類文件由兩個不同的類加載器加載,則JVM內(nèi)會創(chuàng)建兩個具有相同名稱但是定義的不同的類。由于兩個類不相同,所以一個類的實例不能被分配給另一個類的變量,兩個類之間的轉(zhuǎn)換操作也會失敗并且拋出一個ClassCastException異常。
總結(jié)Javassist比我們在本文中所討論的功能要豐富得多,作為jboss的一個子項目,其主要的優(yōu)點在于簡單和快速,可以直接使用java編碼的形式,而不需要了解虛擬機指令,就能動態(tài)改變類的結(jié)構(gòu),或者動態(tài)生成類。如果你不是很了解虛擬機指令,可以采用javassist。
參考文檔:www.javassist.org/tutorial/tu…
en.wikipedia.org/wiki/Javass…
閱讀博客還不過癮?
歡迎大家掃二維碼通過添加群助手,加入交流群,討論和博客有關(guān)的技術(shù)問題,還可以和博主有更多互動
博客轉(zhuǎn)載、線下活動及合作等問題請郵件至 [email protected] 進(jìn)行溝通
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/7354.html
摘要:是一門最近比較流行的靜態(tài)類型編程語言,而且和一樣同屬系。這個生成的構(gòu)造函數(shù)是合成的,因此不能從或中直接調(diào)用,但可以使用反射調(diào)用。 showImg(https://segmentfault.com/img/remote/1460000012958496); Kotlin是一門最近比較流行的靜態(tài)類型編程語言,而且和Groovy、Scala一樣同屬Java系。Kotlin具有的很多靜態(tài)語言...
摘要:摘要實踐內(nèi)存初探閑魚技術(shù)匠修我們想使用來統(tǒng)一移動開發(fā)并做了一些實踐。將內(nèi)存管理分為新生代和老年代。在標(biāo)記階段,所有線程參與并發(fā)的完成對回收對象的標(biāo)記,降低標(biāo)記階段耗時。的首幀渲染耗時較高,在版本有明顯感受,大概會黑屏秒,版本會好很多。 摘要: Android Flutter實踐內(nèi)存初探 閑魚技術(shù)-匠修我們想使用Flutter來統(tǒng)一移動App開發(fā)并做了一些實踐。移動設(shè)備上的資源有限,通常...
摘要:以我們的程序為例,就是以為產(chǎn)生了一個名為的新類型,改類型的實現(xiàn)由給出,而就包含了通過返回的這個方法。從中找到這些類并一一執(zhí)行測試。 先以一個大牛的一段關(guān)于Python Metapgramming的著名的話來做開頭: Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder ...
摘要:有了測試腳本,通過線程組來模擬真實用戶對服務(wù)器的訪問壓力。不同的是,這些類型的線程執(zhí)行測試結(jié)束后執(zhí)行定期的線程組。線程組中包含的線程數(shù)量在測試執(zhí)行過程中是不會發(fā)生改變的。邏輯控制器元件只對其子節(jié)點中的取樣器和邏輯控制器作用。 工欲善其事必先利其器,要保證移動應(yīng)用產(chǎn)品在上線之后能穩(wěn)定運行于各種復(fù)雜環(huán)境,僅僅進(jìn)行功能測試是遠(yuǎn)遠(yuǎn)不夠的,壓力測試越來越被應(yīng)用開發(fā)商所重視。而壓力測試從傳統(tǒng)的內(nèi)部...
摘要:語言在之前,提供的唯一的并發(fā)原語就是管程,而且之后提供的并發(fā)包,也是以管程技術(shù)為基礎(chǔ)的。但是管程更容易使用,所以選擇了管程。線程進(jìn)入條件變量的等待隊列后,是允許其他線程進(jìn)入管程的。并發(fā)編程里兩大核心問題互斥和同步,都可以由管程來幫你解決。 并發(fā)編程這個技術(shù)領(lǐng)域已經(jīng)發(fā)展了半個世紀(jì)了。有沒有一種核心技術(shù)可以很方便地解決我們的并發(fā)問題呢?這個問題, 我會選擇 Monitor(管程)技術(shù)。Ja...
閱讀 1415·2021-09-13 10:25
閱讀 596·2019-08-30 15:53
閱讀 2296·2019-08-30 15:44
閱讀 2066·2019-08-29 17:20
閱讀 1624·2019-08-29 16:36
閱讀 1826·2019-08-29 14:10
閱讀 1810·2019-08-29 12:44
閱讀 1198·2019-08-23 14:13