編譯器 API 加載Java文件特性

一.運行時編譯java文件

在 JDK 6 中,類庫通過 javax.tools 包提供了程序運行時調用編譯器的 API。從這個包的名字 tools 可以看出,這個開發包提供的功能並不僅僅限於編譯器。工具還包括 javah、jar、pack200 等,它們都是 JDK 提供的命令行工具。這個開發包希望通過實現一個統一的接口,可以在運行時調用這些工具。在 JDK 6 中,編譯器被給予了特別的重視。針對編譯器,JDK 設計了兩個接口,分別是 JavaCompiler 和 JavaCompiler.CompilationTask

下面給出一個例子,展示如何在運行時調用編譯器。

  • 指定編譯文件名稱(該文件必須在 CLASSPATH 中可以找到):String fullQuanlifiedFileName = "compile" + java.io.File.separator +"Target.java";
  • 獲得編譯器對象: JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

通過調用 ToolProvider 的 getSystemJavaCompiler 方法,JDK 提供了將當前平臺的編譯器映射到內存中的一個對象。這樣使用者可以在運行時操縱編譯器。JavaCompiler 是一個接口,它繼承了 javax.tools.Tool 接口。因此,第三方實現的編譯器,只要符合規範就能通過統一的接口調用。同時,tools 開發包希望對所有的工具提供統一的運行時調用接口。相信將來,ToolProvider 類將會爲更多地工具提供 getSystemXXXTool 方法。tools 開發包實際爲多種不同工具、不同實現的共存提供了框架。

  • 編譯文件:int result = compiler.run(null, null, null, fileToCompile);

獲得編譯器對象之後,可以調用 Tool.run 方法對源文件進行編譯。Run 方法的前三個參數,分別可以用來重定向標準輸入、標準輸出和標準錯誤輸出,null 值表示使用默認值。

參考:

public class Target {
        public void doSomething(){
                Date date = new Date(10, 3, 3); 
                 // 這個構造函數被標記爲deprecated, 編譯時向錯誤輸出輸出信息。
                 System.out.println("Doing");
       }
}

public class Compiler {
public static void main(String[] args) throws Exception {
String fullQuanlifiedFileName = "Target.java";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
FileOutputStream err = new FileOutputStream("err.txt");
int compilationResult = compiler.run(null, null, err, fullQuanlifiedFileName);
if (compilationResult == 0) {
System.out.println("Done");
} else {
System.out.println("Fail");
}
}
}

編譯非文本形式的文件

JDK 6 的編譯器 API 的另外一個強大之處在於,它可以編譯的源文件的形式並不侷限於文本文件。JavaCompiler 類依靠文件管理服務可以編譯多種形式的源文件。比如直接由內存中的字符串構造的文件,或者是從數據庫中取出的文件。這種服務是由 JavaFileManager 類提供的。通常的編譯過程分爲以下幾個步驟:

  1. 解析 javac 的參數;
  2. 在 source path 和/或 CLASSPATH 中查找源文件或者 jar 包;
  3. 處理輸入,輸出文件;

在這個過程中,JavaFileManager 類可以起到創建輸出文件,讀入並緩存輸出文件的作用。由於它可以讀入並緩存輸入文件,這就使得讀入各種形式的輸入文件成爲可能。JDK 提供的命令行工具,處理機制也大致相似,在未來的版本中,其它的工具處理各種形式的源文件也成爲可能。爲此,新的 JDK 定義了 javax.tools.FileObject 和javax.tools.JavaFileObject 接口。任何類,只要實現了這個接口,就可以被 JavaFileManager 識別。

如果要使用 JavaFileManager,就必須構造 CompilationTask。JDK 6 提供了 JavaCompiler.CompilationTask 類來封裝一個編譯操作。這個類可以通過:

JavaCompiler.getTask (
    Writer out, 
    JavaFileManager fileManager,
    DiagnosticListener
<? super JavaFileObject> diagnosticListener,
    Iterable
<String> options,
    Iterable
<String> classes,
    Iterable
<? extends JavaFileObject> compilationUnits
)

方法得到。關於每個參數的含義,請參見 JDK 文檔。傳遞不同的參數,會得到不同的 CompilationTask。通過構造這個類,一個編譯過程可以被分成多步。進一步,CompilationTask提供了 setProcessors(Iterable<? extends Processor>processors) 方法,用戶可以制定處理 annotation 的處理器。圖 1 展示了通過 CompilationTask 進行編譯的過程:

public class Compiler {
public static void main(String[] args) throws Exception {
String fullQuanlifiedFileName = "Target.java";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable<?extendsJavaFileObject>files=                     fileManager.getJavaFileObjectsFromStrings(Arrays.asList(fullQuanlifiedFileName));
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, files);
Boolean result = task.call();
if (result == true) {
System.out.println("Succeeded");
}
}

}

以上是第一步,通過構造一個 CompilationTask 編譯了一個 Java 文件。首先取得一個編譯器對象。由於僅僅需要編譯普通文件,通過編譯器對象取得了一個標準文件管理器。將需要編譯的文件構造成了一個 Iterable 對象。最後將文件管理器和 Iterable 對象傳遞給 JavaCompiler 的 getTask 方法,取得了 JavaCompiler.CompilationTask 對象。

接下來第二步,開發者希望生成 Calculator 的一個測試類,而不是手工編寫。使用 compiler API,可以將內存中的一段字符串,編譯成一個 CLASS 文件。

public class StringObject extends SimpleJavaFileObject {

private String contents = null;

public Compiler(String className, String contents) throws Exception {
super(new URI(className), Kind.SOURCE);
this.contents = contents;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return contents;
}
}

SimpleJavaFileObject 是 JavaFileObject 的子類,它提供了默認的實現。繼承 SimpleJavaObject 之後,只需要實現 getCharContent 方法。接下來,在內存中構造 Calculator 的測試類 CalculatorTest,並將代表該類的字符串放置到 StringObject 中,傳遞給 JavaCompiler 的 getTask 方法。

 public class AdvancedCompiler {
       // Steps used to compile Calculator
     // Steps used to compile StringObject
     // construct CalculatorTest in memory
public static void main(String[] args) throws Exception{
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager  = compiler.getStandardFileManager(null, null, null);
String contents="public class Target {\r\n" + 
"        public void doSomething(){\r\n" + 
"                Date date = new Date(10, 3, 3); \r\n" + 
"                 // 這個構造函數被標記爲deprecated, 編譯時向錯誤輸出輸出信息。\r\n" + 
"                 System.out.println(\"Doing\");\r\n" + 
"       }\r\n" + 
"}";
             JavaFileObject file =  new StringObject("Target", contents.toString());
             Iterable<? extends JavaFileObject> files = Arrays.asList(file);
             JavaCompiler.CompilationTask task = compiler.getTask ( null, fileManager, null, null, null, files);
             Boolean result = task.call();
             if( result == true ) {
               System.out.println("Succeeded");
             }
       }
 }

程序在內存中構造了 Target類,並且通過 StringObject 的構造函數,將內存中的字符串,轉換成了 JavaFileObject 對象。

通過fileManager 獲取class文件內容

JavaClassObject jco = fileManager.getMainJavaClassObject();

List<JavaClassObject> innerClassJcos = fileManager.getInnerClassJavaClassObject();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章