動態編譯JAVA程式

okone96發表於2007-07-19

這段說明很有用,動態的編譯是件非常好的想法,我這裡引用收藏一下:)
地址:http://www.itpub.net/showthread.php?s=&postid=8088670#post8088670

[轉貼者注]對於很多應用系統,常常需要動態裝載和執行類和程式碼片斷,這有利於部署的簡易性和系統設計上的靈活性。本文給出了一個比較全面的介紹,值得參考。

在Sun JDK 1.2及後續版本中,包含了一組可在程式執行時刻編譯和執行Java程式碼的API。這些API被包含在tools.jar類庫中。這個功能允許Java程式在執行時動態編譯、執行小的程式碼塊,在有些情況下這個功能會讓Java應用程式的架構更加靈活、開放。

  本文假定讀者已經在計算機中安裝並配置好了Sun JDK 1.2或更高的版本,並對javac編譯器命令有所瞭解。

  在Java程式中使用編譯器
  假定要使用javac命令編譯 /home/mytest目錄下Test.java檔案,並設定class檔案存放在/home/mytest/classes路徑下,輸入下面命令:

  javac -d /home/mytest/classes Test.java

  達到同樣的目的,也可以使用Sun提供的一個Java編譯器的API來實現。它的使用也很簡單,核心程式碼段如下:



  …

  String[] args = new String[] {“-d”, “/homemytestclasses”, “Test.java”};

  Int status = javac.compile(args);

  …


  javac編譯工具被安裝在JDK根目錄的/bin目錄下,負責將原始碼編譯成執行於JVM的位元組碼。事實上,我們經常使用/bin目錄下的javac編譯工具來編譯Java原始檔。如果在Java程式中動態編譯任意制定的Java語句,使用這個外部的javac編譯器就顯得不夠靈活了。雖然有時可使用Runtime類來執行一個外部命令,但如果想知道程式碼是否被編譯透過、編譯時發生了什麼錯誤,用Runtime類的exec()方法就很難實現了。

  在Sun的JDK 1.2及後續版本中,JDK安裝路徑的/lib路徑下包含了一個tools.jar檔案,這個類庫包含了一個完整的編譯器包。com.sun.tools.javac.Main是編譯器的主類入口,如果已經熟悉了javac編譯器命令列的使用方法,很容易理解這個類的使用方法。方法compile(String[] p)執行編譯動作,引數p是一個String陣列,用來存放javac命令的引數選項,編譯後的狀態返回一個Int值,其對應值參考如下表所示:

  表 狀態引數與對應值

  EXIT_OK 0

  EXIT_ERROR 1

  EXIT_CMDERR 2

  EXIT_SYSERR 3

  EXIT_ABNORMAL 4

  

  在程式執行時編譯和執行Java語句

  從上面一段中,我們已經基本瞭解了動態編譯一個Java檔案的方法。那麼,如何執行時動態編譯指定的Java語句呢?這裡需要一個技巧。

  假設要動態編譯的Java條語句如下:



  System.out.println(“Hello,This runtime code!”);


  編譯器不支援編譯單個Java語句,被編譯的物件必須是一個以.java為字尾的、結構合法的類源程式檔案,所以需要對這個語句進行改造,變成一個完整的類,並把這條語句置入main方法中,便於測試。



  public class {

  public static void main(String[] args) throws Exception {

  System.out.println(“Hello,This runtime code!”);

  }

  }


  這樣,欲動態編譯的程式碼已經被程式動態拼裝成了上面那段程式碼,準備工作還沒有結束,不過看起來工作在趨向稍微的複雜化。因為上述程式碼當前還存放在記憶體中,編譯器似乎對一個硬碟檔案更感興趣。我們需要引用java.io.File類(JDK 1.2以上),建立一個臨時的檔案來存放上述程式碼的內容。java.io.File類的靜態方法createTempFile()方法保證所建立的檔名是不重複的,這樣會增大這段程式的靈活性。靈活性取決於真正應用到系統架構中的策略。

  System.getProperty(“user.dir”)用來獲得當前路徑,在這裡作為臨時檔案的存放目錄。



  File file;

  file = File.createTempFile(“JavaRuntime”, “.java”, new File(System.getProperty(“user.dir”)));

  String filename = file.getName();

  String classname = getClassName(filename);

  //將程式碼輸出到檔案

  PrintWriter out = new PrintWriter(new FileOutputStream(file));

  out.println(“public class” + classname + “ {”};

  out.println(“..程式碼..”);

  out.println(“}”);

  //關閉檔案流

  out.flush();

  out.close();

  我們約定被建立的臨時檔名以“JavaRuntime”為頭綴(可任意命名),字尾名以“.java”結尾。一個待編譯的Java原始檔已被動態生成。下一步要從com.sun.tools.javac包中建立一個Main例項,呼叫javac.compile()方法編譯這個臨時檔案:



  Private static com.sun.tools.javac.Main javac = new com.sun.tools.javac.Main();

  String[] args = new String[] {“-d”, System.getProperty(“user.dir”),filename };

  Int status = javac.compile(args);


  假定臨時檔案透過了編譯器文法驗證等驗證,編譯成功(status值等於0,參看前表),在當前程式的執行目錄下就會多了一個Java類檔案。我們將透過執行這個Java 類檔案,來模擬執行欲動態編譯程式碼的結果。

  Java提供在執行時刻載入類的特性,可動態識別和呼叫類構造方法、類欄位和類方法。java.lang.reflect.Method實現了Member介面,可以呼叫介面的方法來獲得方法類的名稱、修飾詞等。方法getRuturnType()、getParameterTypes()、getExeptionTypess()等返回被表示方法的構造資訊。Method另一個重要的特性是可以呼叫invoke()執行這個方法(詳細使用方法可以檢視java.lang.reflect包文件)。下面這段程式碼中建立一個java.lang.reflect.Method類方法,呼叫getMethod()方法獲得被拼裝的main方法的對映,這段程式碼如下:



  try {

  // 訪問這個類

  Class cls = Class.forName(classname);

  //呼叫main方法

  Method main = cls.getMethod(“main”, new Class[] { String[].class });

  main.invoke(null, new Object[] { new String[0] });

  }catch (SecurityException se) {

  debug(“access to the information is denied:” + se.toString());

  }catch (NoSuchMethodException nme) {

  debug(“a matching method is not found or if then name is or :

  ” + nme.toString());

  }catch (InvocationTargetException ite) {

  debug(“Exception in main: ” + ite.getTargetException());

  }catch (Exception e){

  debug(e.toString());

  }

  執行結果參如下:

  Hello,This runtime code!

  

  示範程式

  下面給出了一個簡單的Java程式,這個程式說明了如何利用Sun的javac編譯器完成動態編譯Java語句。執行該程式需要計算機安裝JDK 1.2以上版本,並在classpath中或執行時指定tools.jar檔案位置。

  程式結構:

  ◆ compile() 編譯Java程式碼,返回生成的臨時檔案;

  ◆ run()執行編譯的class檔案;

  ◆ debug()輸出除錯資訊;

  ◆ getClassName()從一個Java原始檔獲得類名;

  ◆ readLine()從控制檯讀取使用者輸入的Java Code。



  Import java.io.File;

  …

  Public class RuntimeCode{

  /**編譯器*/

  private static com.sun.tools.javac.Main javac = new com.sun.tools.javac.Main();

  /**等待使用者輸入JavaCode,然後編譯、執行*/

  public static void main(String[] args) throws Exception{

  …

  run(compile(code));

  }

  /**編譯JavaCode,返回臨時檔案物件*/

  private synchronized static File compile(String code)

  throws IOException,Exception {

  File file;

  //在使用者當前檔案目錄建立一個臨時程式碼檔案

  file = File.createTempFile(“JavaRuntime”, “.java”,

  new File(System.getProperty(“user.dir”)));

  //當虛擬機器退出時,刪除此臨時java原始檔

  file.deleteOnExit();

  //獲得檔名和類名字

  String filename = file.getName();

  String classname = getClassName(filename);

  //將程式碼輸出到檔案

  PrintWriter out = new PrintWriter(new FileOutputStream(file));

  out.println(“/**”);

  …

  //關閉檔案流

  out.flush();

  out.close();

  //編譯程式碼檔案

  String[] args = new String[] {“-d”, System.getProperty(“user.dir”),filename };

  //返回編譯的狀態程式碼

  int status = javac.compile(args);

  //處理編譯狀態

  …

  }

  /**執行剛剛編譯的類檔案*/

  private static synchronized void run(File file)

  …

  //當虛擬機器退出時,刪除此臨時編譯的類檔案

  new File(file.getParent(), classname + “.class”).deleteOnExit();

  try {

  // 訪問這個類

  Class cls = Class.forName(classname);

  //對映main方法

  Method main = cls.getMethod(“main”, new Class[] { String[].class });

  //執行main方法

  main.invoke(null, new Object[] { new String[0] });

  }catch (SecurityException se) {

  …

  }

  }

  /**列印除錯資訊*/

  private static void debug(String msg) {

  System.err.println(msg);

  }

  /**根據一個java原始檔名獲得類名*/

  private static String getClassName(String filename){

  return filename.substring(0,filename.length()-5);

  }

  /**從控制檯獲得使用者輸入的Java程式碼段*/

  …

  }

  編譯執行上述程式碼,在please input java code提示下輸入以下程式碼:

  for(int i=0;i<10;i++){System.out.println(“this is:”+i);}

  執行結果如下所示:

  Please input java code:

  for(int i=0;i<10;i++){System.out.println(“this is:”+i);}

  wait....

  --------------------

  this is:0

  this is:1

  this is:2

  this is:3

  this is:4

  this is:5

  this is:6

  this is:7

  this is:8

  this is:9

  

  總結

  在大中型企業應用系統平臺中,使用程式碼動態編譯技術結合OO程式設計模型,可在系統不菪機條件下保證系統的可擴充套件性和伸縮性。如果你是一個Java程式設計師,稍加調整以上程式碼,還可以幫助除錯小段的Java程式碼.

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/750220/viewspace-927053/,如需轉載,請註明出處,否則將追究法律責任。

相關文章