動態編譯Java源文件
壹般來說,在程序運行之前,開發者已經寫好了所有的Java源代碼,並且編譯成功。對於某些應用程序,Java源代碼的內容只能在運行時確定。這時候就需要動態編譯源代碼生成Java字節碼,然後由JVM加載執行。壹個典型的場景是很多算法競賽的在線評測系統(比如PKU JudgeOnline),允許用戶上傳Java代碼,系統在後臺編譯、運行、評判。動態編譯Java源文件時,采用的方法是在程序中直接調用Java編譯器。
JSR 199引入了Java編譯器API。如果妳使用JDK 6,妳可以通過這個API動態編譯Java代碼。例如,下面的代碼用於動態編譯最簡單的Hello World類。這個Java類的代碼存儲在壹個字符串中。
01公共類編譯器測試{
02公共靜態void main(String[] args)引發異常{
03 String source = " public class Main { public static void Main(String[]args){ system . out . println(\ " Hello World!\");} }";
04 Java compiler compiler = tool provider . getsystemjavacompiler();
05 StandardJavaFileManager file manager = compiler . getstandardfilemanager(null,null,null);
06 StringSourceJavaObject source object = newCompilerTest。StringSourceJavaObject("Main ",source);
07 Iterable & lt擴展JavaFileObject & gtfile objects = arrays . aslist(source object);
08 compilation task task = compiler . get task(null,fileManager,null,null,null,file objects);
09布爾結果= task . call();
10如果(結果){
11系統。out.println("編譯成功。");
12 }
13 }
14
15靜態類StringSourceJavaObject擴展SimpleJavaFileObject {
16
17私有字符串內容= null
18 public StringSourceJavaObject(字符串名稱,字符串內容)throwsURISyntaxException {
19 super(uri . create(" string:///"+name . replace(' . '),'/') + Kind。SOURCE.extension),種類。來源);
20 this.content =內容;
21 }
22
23 public char sequence getCharContent(boolean ignore encodingerrors)拋出IOException {
24返回內容;
25 }
26 }
27 }
如果不能使用JDK 6提供的Java編譯器API,可以使用JDK的工具類com.sun.tools.javac.Main,但是這個工具類只能編譯存儲在磁盤上的文件,類似於直接使用javac命令。
另壹個可用的工具是Eclipse JDT Core提供的編譯器。這是壹個Eclipse Java開發環境使用的增量式Java編譯器,支持帶錯運行調試代碼。編譯器也可以單獨使用。Play框架內部使用JDT編譯器動態編譯Java源代碼。在開發模式下,Play框架會定期掃描項目中的Java源代碼文件,壹旦發現有變化,就會自動編譯Java源代碼。因此,在修改代碼後,您可以通過刷新頁面來查看更改。當使用這些動態編譯方法時,需要確保JDK中的tools.jar在應用程序的類路徑中。
下面舉例說明如何用Java做四則運算,比如求(3+4)*7-10的值。壹般的做法是分析輸入的運算表達式,自己模擬計算過程。考慮到括號的存在和運算符的優先級,這樣的計算過程會比較復雜,容易出錯。另壹種方式是使用JSR 223引入的腳本語言,以JavaScript或JavaFX script的形式直接執行輸入的表達式,並得到結果。下面的代碼用來動態生成Java源代碼並編譯,然後加載Java類執行並得到結果。這個方法完全是用Java實現的。
01私有靜態double calculate(String expr)拋出CalculationException {
02 String class name = " calculator main ";
03字符串methodName = " calculate
04 String source = " public class "+class name
05+" { public static double "+method name+"(){ return "+expr+";} }";
06 //省略動態編譯Java源代碼相關的代碼,見上壹節。
07布爾結果= task . call();
08如果(結果){
09 class loader loader = calculator . class . get class loader();
10嘗試{
11 Class & lt;?& gtclazz = loader . load class(class name);
12 Method Method = clazz . get Method(Method name,new Class & lt?& gt[] {});
13 Object value = method . invoke(null,new Object[]{ });
14返回(Double)值;
15 } catch(例外e) {
16拋出新的計算異常("內部錯誤。");
17 }
18 }其他{
19拋出新的計算異常("表達式錯誤。");
20 }
21 }
上面的代碼給出了使用動態生成的Java字節碼的基本模式,即通過類加載器加載字節碼,創建壹個Java類的對象的實例,然後通過Java反射API調用對象中的方法。
Java字節碼增強
Java字節碼增強是指在Java字節碼生成後對其進行修改,以增強其功能。這相當於修改應用程序的二進制文件。這個實現可以在很多spring mvc中看到。Java字節碼增強通常與Java源文件中的註釋壹起使用。註解聲明Java源代碼中需要增強的行為和相關元數據,框架在運行時完成字節碼的增強。Java字節碼增強應用程序有許多場景,它們通常側重於減少冗余代碼,並保護開發人員免受底層實現細節的影響。使用過JavaBeans的人可能會發現那些必須添加的getter/setter方法很麻煩,很難維護。通過字節碼增強,開發人員只需在Bean中聲明屬性,getter/setter方法可以通過修改字節碼自動添加。用過JPA的人會發現,在調試程序時,實體類中增加了壹些額外的字段和方法。這些字段和方法是由JPA的實現在運行時動態添加的。在AOP的壹些實現中也使用了字節碼增強。