婁不夜 首都經(jīng)濟貿(mào)易大學信息學院 100070
Web環(huán)境下Java表達式的動態(tài)編譯與計算
婁不夜 首都經(jīng)濟貿(mào)易大學信息學院 100070
針對Java Web應用程序需要在運行時從外部讀得Java表達式并進行計算的要求,利用編譯器API對源代碼進行編譯、采用自定義類裝載器裝載字節(jié)代碼、基于反射機制執(zhí)行字節(jié)代碼,從而實現(xiàn)Java表達式的動態(tài)編譯與計算。該方法不需要產(chǎn)生Java源文件或class文件。實際系統(tǒng)的具體應用驗證了該方法的有效性。
Java表達式;編譯器API;類裝載器;反射機制
有些Java Web應用程序在運行時需要從外部(如數(shù)據(jù)庫、XML文件)讀入某些數(shù)據(jù),以完成相應的處理功能。這些數(shù)據(jù)可能是一些簡單的字符串、數(shù)值,也可能是一些需要計算才能得到結(jié)果的表達式。
大多數(shù)解釋型語言都有諸如eval的函數(shù),可以將一個字符串作為表達式進行計算求值,而作為半編譯半解釋的Java語言并不提供類似的功能。本文介紹一種利用Java SE6提供的編譯器API實現(xiàn)類似功能的方法,可以在程序運行過程中完成Java表達式的編譯和計算。
該方法涉及動態(tài)編譯、自定義類裝載器、反射機制等技術,其基本過程如下:
(1) 基于要計算的表達式字符串構(gòu)建一個Java類,類的源代碼存儲在普通的字符串中。
(2) 利用編譯器API實現(xiàn)對源代碼的動態(tài)編譯。其中源代碼直接讀自上述字符串,編譯產(chǎn)生的字節(jié)代碼保存在字節(jié)數(shù)組中。整個過程不涉及源代碼文件和字節(jié)代碼文件的創(chuàng)建。
(3) 使用自定義類裝載器裝入字節(jié)代碼、產(chǎn)生Class對象。這里,類裝載器直接從字節(jié)數(shù)組讀取字節(jié)代碼。由于Java不支持類的重新裝載,也不允許已裝載類的卸載,所以每次計算表達式都需要新建一個類裝載器實例。
(4) 利用反射機制調(diào)用Temp類的m方法,完成表達式的最后計算。
其中,字符串exp是要計算的表達式,靜態(tài)方法m的功能是計算并返回表達式的值。這里表達式的類型可以是任意引用類型或基本類型。若是基本類型,計算結(jié)果會自動轉(zhuǎn)換成相應包裝類對象返回。
(1) 調(diào)用javax.tools.ToolProvider類的getSystemJavaCompiler方法獲得編譯器對象。
(2) 調(diào)用編譯器對象的getTask方法創(chuàng)建編譯作業(yè)對象。getTask方法有六個參數(shù),其中第2個參數(shù)需指定一個Java文件管理器,在執(zhí)行編譯作業(yè)時,系統(tǒng)會自動調(diào)用該文件管理器的相關方法獲取用于保存編譯產(chǎn)生的字節(jié)代碼的Java文件對象。第4個參數(shù)指定編譯所需的相關參數(shù)。第6個參數(shù)需指定包含待編譯源代碼的Java文件對象。其他三個參數(shù)可置為null。
(3) 調(diào)用編譯作業(yè)對象的call方法,執(zhí)行編譯作業(yè)。若方法返回true,表示編譯成功;否則表示編譯失敗。
(4) 調(diào)用Java文件管理器的相關方法獲取保存有字節(jié)代碼的Java文件對象,然后調(diào)用Java文件對象的相關方法獲取字節(jié)代碼。
Java文件對象是對Java源代碼或Java字節(jié)代碼的抽象表示。默認情況下,一個Java文件對象表示一個Java源文件或一個class文件。為能表示保存在內(nèi)存(如字符串、字節(jié)數(shù)組)的Java源代碼或字節(jié)代碼,需要自定義Java文件對象類。
為清晰之見,對兩種Java文件對象類分別進行定義。用于表示源代碼的Java文件對象類的代碼如下:
在執(zhí)行編譯作業(yè)時,系統(tǒng)會自動調(diào)用getCharContent方法獲得源代碼。
用于表示字節(jié)代碼的Java文件對象類的部分代碼如下:
當執(zhí)行編譯作業(yè)時,系統(tǒng)會自動調(diào)用openOutputStream方法獲得一個字節(jié)輸出流,并通過該字節(jié)輸出流寫出編譯產(chǎn)生的字節(jié)代碼。編譯結(jié)束后,可以調(diào)用getByteCode方法獲取字節(jié)代碼。
Java文件管理器用于管理Java文件對象。在創(chuàng)建編譯作業(yè)對象時,若第2個參數(shù)設置為null,系統(tǒng)將采用一個標準Java文件管理器。在這種情況下,編譯產(chǎn)生的字節(jié)代碼將被保存到相應的class文件中。為實現(xiàn)對特殊Java文件對象的管理,需要自定義Java文件管理器類(省略了構(gòu)造方法):
這種類型的Java文件管理器可以對JavaFileObjectByteCode型文件對象進行管理。當執(zhí)行編譯作業(yè)時,系統(tǒng)會自動調(diào)用getJavaFileForOutput方法獲得用于保存相應字節(jié)代碼的文件對象。編譯結(jié)束后,用戶也可以調(diào)用該方法獲得相同的文件對象。
要執(zhí)行Java字節(jié)代碼,首先需要由類裝載器將其裝入JVM。在JVM中,通常存在多個類裝載器,每個類裝載器負責裝載特定位置的class文件。最基本的類裝載器包括:
(1) 引導(Bootstrap)類裝載器。該裝載器屬于JVM的一部分,其本身在JVM啟動時自行裝入。作用有兩個:一是裝載并創(chuàng)建擴展類裝載器和應用程序類裝載器;二是需要時裝載Java核心類庫中的class文件。
(2) 擴展(Extension)類裝載器。用于裝載java.ext.dirs屬性指定位置處的class文件。
(3) 應用程序(Application)類裝載器。用于裝載java.class.path屬性指定位置處的class文件。
其中擴展類裝載器代碼和應用程序類裝載器代碼都是Java類,它們都是ClassLoader類子類。
每個類裝載器在創(chuàng)建時可以指定一個父類裝載器,如擴展類裝載器就被指定為應用程序類裝載器的父類裝載器。裝載一個類通常由系統(tǒng)隱含調(diào)用類裝載器的loadClass(String)方法完成,其過程采用委托模型:首先檢查當前類裝載器是否已裝載該類;若沒有,就委托其父類裝載器進行裝載;若沒有父類裝載器,則委托引導類裝載器進行裝載;若上述所有情況都無法定位或裝載該類,就調(diào)用當前類裝載器的findClass(String)方法自行裝載。
Web應用環(huán)境一般會有更多的類裝載器,這些類裝載器通常把應用程序類裝載器作為父類裝載器,它們同樣采用上述委托模型進行類的裝載。
為了能將由動態(tài)編譯產(chǎn)生的、保存在字節(jié)數(shù)組中的Temp類的字節(jié)代碼裝入JVM,需要自定義類裝載器類:
這里,loadClass(byte[])方法特用于裝載Temp類的字節(jié)代碼,而Temp類引用的其他類型仍由系統(tǒng)隱含調(diào)用定義在超類中的loadClass(String)方法裝載,實質(zhì)上就是委托父類裝載器或引導類裝載器裝載。
只要父類裝載器和編譯參數(shù)(getTask方法的第4個參數(shù))設置得當,Temp類和需要計算的表達式就可以引用任何應用程序能夠引用的類型。
裝入Temp類的字節(jié)代碼后,就可以利用反射機制執(zhí)行其中的靜態(tài)方法,完成表達式的計算求值:
這里為基于JSF框架的Web應用設計一個名為Calculator的應用程序范圍的托管Bean,其他托管Bean可以調(diào)用其calculate(String)方法計算指定的Java表達式:
其中,createSource和execute方法的代碼已在前面給出,calculate(String)方法除需調(diào)用上面兩個方法外,主要是要實現(xiàn)代碼的動態(tài)編譯,其處理過程與相關技術在前面已進行了詳細介紹,這里不再贅述。
該托管Bean類還定義了若干有名常量。這些常量在每次計算表達式時都是通用的,可以在構(gòu)造方法中進行設置。這些常量的作用如下:
compiler:編譯器對象。用于創(chuàng)建編譯作業(yè)對象以及標準Java文件管理器。
sm:標準Java文件管理器。在創(chuàng)建自定義的FileManagerImpl型文件管理器時,應將該標準文件管理器作為參數(shù)傳遞給構(gòu)造方法。
options:作為編譯器對象的getTask方法的第4個參數(shù),可包含一些編譯參數(shù)。如將classpath參數(shù)設為Web應用中WEB-INFclasses目錄的實際路徑。
parent:裝載當前Bean類的類裝載器。可作為自定義類裝載器的父類裝載器。
本文采用編譯器API、自定義類裝載器、反射機制等技術實現(xiàn)了Java表達式的動態(tài)編譯和計算。該方法已在實際系統(tǒng)中得到了驗證和應用,具有通用、便捷、性能好等特點,達到了滿意的效果。
[1]David Biesack. 使用javax.tools創(chuàng)建動態(tài)應用程序[EB/OL]. http://www.ibm.com/ developerworks/cn/java/j-jcomp/#download, 2007-12-24.
[2]James Gosling, Bill Joy. The Java Language Specification Third Edition[M]. Addison Wesley,2005:308-331.
[3]藏旭毅. 淺析J2EE應用服務器的JAVA類裝載器[J]. 電腦知識與技術.2007, 3(18):1609-1610.
[4]陳燁,張蓓. JDK1.5類庫大全[M]. 北京:清華大學出版社.2005:403-419.
10.3969/j.issn.1001-8972.2010.16.063
首都經(jīng)濟貿(mào)易大學教改項目(00790954210333)
婁不夜(1965-),男,副教授,碩士,研究方向為信息技術與信息系統(tǒng)、Web應用。