Java動態編程初探

做者簡介html

傳恆,一個喜歡攝影和旅遊的軟件工程師,前後從事餓了麼物流蜂鳥自配送和蜂鳥衆包的開發,如今轉戰 Java,目前負責物流策略組分流相關業務的開發。java

什麼是動態編程

動態編程是相對於靜態編程而言的,平時咱們討論比較多的靜態編程語言例如Java, 與動態編程語言例如JavaScript相比,兩者有什麼明顯的區別呢? 簡單的說就是在靜態編程中,類型檢查是在編譯時完成的,而動態編程中類型檢查是在運行時完成的, 所謂動態編程就是繞過編譯過程在運行時進行操做的技術。編程

動態編程使用場景

  • 經過配置生成代碼,減小重複編碼,下降維護成本。
  • AOP的一種實現方式,方便實現性能監控和分析,日誌,事務,權限校驗等。
  • 實現新語言的語義,例如Groovy使用ASM生成字節碼。
  • 單元測試中動態mock測試依賴。

在Java中有以下幾種方式實現動態編程:

反射

咱們經常使用到的動態特性主要是反射,在運行時查找對象的屬性和方法,修改做用域,經過方法名稱調用方法等。在線的應用不建議頻繁使用反射,由於反射的性能開銷較大。服務器

動態代理

在java的java.lang.reflect包下提供了一個Proxy類和一個InvocationHandler接口,經過這個類和這個接口能夠生成JDK動態代理類和動態代理對象。架構

動態編譯

動態編譯是從Java 6開始支持的,主要是經過一個JavaCompiler接口來完成的。經過這種方式咱們能夠直接編譯一個已經存在的java文件,也能夠在內存中動態生成Java代碼,動態編譯執行。框架

調用Java Script引擎

Java 6加入了對Script(JSR223)的支持。這是一個腳本框架,提供了讓腳本語言來訪問Java內部的方法。你能夠在運行的時候找到腳本引擎,而後調用這個引擎去執行腳本,這個腳本API容許你爲腳本語言提供Java支持。編程語言

動態生成字節碼

操做java字節碼的工具備BECL/ASM/CGLIB/Javassist,其中有兩個比較流行的,一個是ASM,一個是Javassist。 ASM直接操做字節碼指令,執行效率高,要求使用者掌握Java類字節碼文件格式及指令,對使用者的要求比較高。 Javassist提供了更高級的API,執行效率相對較差,但無需掌握字節碼指令的知識,對使用者要求較低,因此接下來咱們重點講講Javassist。編輯器

Javassist

Javassist是一個開源的分析、編輯和建立Java字節碼的類庫。 它是由東京工業大學的數學和計算機科學系的 Shigeru Chiba (千葉滋) 所建立的,目前已經加入到開放源代碼JBoss應用服務器項目,JBoss經過使用Javassist對字節碼進行操做,實現動態AOP框架。

Javassist(Java Programming Assistant) 使對Java字節碼的操做變得簡單,它使Java程序可以在運行時定義新類,而且能夠在JVM加載時修改類文件。 與其它相似的字節碼編輯器不一樣,它提供兩個級別的API:源級別和字節碼級別。 若是用戶使用源級別API,他們能夠在不知道Java字節碼規範的狀況下編輯類文件。整個API僅使用Java語言的詞彙表進行設計,你甚至可使用Java源代碼的方式插入字節碼。 另外,用戶也可使用字節碼級別的API去直接編輯類文件。工具

// ClassPool 是 CtClass 對象的容器,存儲着CtClass的Hash表。它按需讀取類文件來構造CtClass對象,而且保存CtClass對象以便以後使用
ClassPool classPool = ClassPool.getDefault();
// CtClass 表示一個class文件,一個 GtClass(compile-time class)對象用來處理一個class文件,下面是從classpath中查找該類
CtClass ctClass = classPool.get("test.config.ConfigHandle");
// 通知編輯器去尋找對應的包
classPool.importPackage("org.mockito.Mockito");
classPool.importPackage("test.adapter.ext.IDowngrade");
classPool.importPackage("test.utils.property.IProperties");
// 使用removeField() removeMethod() 去刪除對應的屬性和方法
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);\n" +
    " this.myConfig = Mockito.mock(IProperties.class);}");
// toClass() 請求當前線程的 ClassLoader 去加載 CtClass 所表明的類文件
ctClass.toClass();
//輸出成二進制格式
//byte[] b = ctClass.toBytecode();
//輸出class文件到目錄中
//ctClass.writeFile("/tmp");

複製代碼

ClassPool是CtClass對象的容器,由於編譯器在編譯引用CtClass表明的Java類的源代碼時,可能會引用CtClass對象,因此一旦一個CtClass被建立,它就被保存在ClassPool中。性能

若是事先知道要修改哪些類,修改類的最簡單方法以下:

    1. 調用 ClassPool.get() 獲取 CtClass 對象
    1. 修改對象
    1. 調用 CtClass 對象的 writeFile() 或者 toBytecode() 得到修改過的類文件。

若是須要定義一個新類,只須要

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("HelloWorld");
複製代碼

凍結classes

若是一個 CtClass 對象經過 writeFile(), toClass(), toBytecode()被轉換成一個類文件,該CtClass對象會被凍結起來,不容許再修改,由於一個類只能被JVM加載一次。

CtClasss cc = ...;
    :
cc.writeFile();
cc.defrost();
cc.setSuperclass(...);    // 類已經被解凍
複製代碼

Class 搜索路徑:

經過 ClassPool.getDefault() 獲取的ClassPool默認使用JVM的類搜索路徑。若是程序運行在JBoss或者Tomcat等Web服務器上,ClassPool可能沒法找到用戶本身定義的類,由於這種Web服務器使用多個類加載器做爲系統類加載器。在這種狀況下,ClassPool必須添加額外的類搜索路徑。

pool.insertClassPath(new ClassClassPath(this.getClass())); // 當前的類使用的類路徑,註冊到類搜索路徑
pool.insertClassPath("/usr/local/javalib"); // 添加目錄 /usr/local/javalib 到類搜索路徑
ClassPath cp = new URLClassPath("www.javassist.org", 80, "/java/", "org.javassist.");
pool.insertClassPath(cp); // 註冊URL到搜索路徑
複製代碼

在Java中,多個類加載器是能夠共存的。每一個類加載器建立了本身的命名空間,不一樣的類加載器能夠加載具備相同類名的不一樣類文件,被加載的類也會被視爲不一樣的類。此功能使咱們可以在單個JVM上面運行多個應用程序,即便這些程序包含具備相同名稱的類。

注意,JVM不容許動態從新加載類,一旦類加載器加載了一個類,就不能再在運行時從新加載該類的其它版本。所以,在JVM加載類以後,就不能再更改該類的定義。 可是,JPDA(Java平臺調試器架構)提供有限的從新加載類的能力,若是相同的類文件由兩個不一樣的類加載器加載,則JVM內會建立兩個具備相同名稱可是定義的不一樣的類。因爲兩個類不相同,因此一個類的實例不能被分配給另外一個類的變量,兩個類之間的轉換操做也會失敗而且拋出一個ClassCastException異常。

總結

Javassist比咱們在本文中所討論的功能要豐富得多,做爲jboss的一個子項目,其主要的優勢在於簡單和快速,能夠直接使用java編碼的形式,而不須要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。若是你不是很瞭解虛擬機指令,能夠採用javassist。

參考文檔:





閱讀博客還不過癮?

歡迎你們掃二維碼經過添加羣助手,加入交流羣,討論和博客有關的技術問題,還能夠和博主有更多互動

博客轉載、線下活動及合做等問題請郵件至 shadowfly_zyl@hotmail.com 進行溝通
相關文章
相關標籤/搜索