從第一天開(kāi)始,標準Java平臺就缺少能夠被調用,去產(chǎn)生Java字節碼的編譯器接口. 使用Sun實(shí)現的平臺,一個(gè)用戶(hù)可以通過(guò)非標準的 com.sun.tools.javac 包中的Main class 去編譯你的代碼 (你可以在lib子目錄下的 tools.jar 文件里找到它). 然而這個(gè)包并沒(méi)有提供一個(gè)標準的公開(kāi)的編程接口. 使用其它實(shí)現的用戶(hù)必然不能訪(fǎng)問(wèn)這個(gè)類(lèi). 使用Java SE 6和在JSR-199中定義的它的新的Java編譯器接口,你可以從你自己的應用程序里訪(fǎng)問(wèn)javac編譯工具了.
有兩種方式使用這種工具. 一種是簡(jiǎn)單的,一種是稍微復雜點(diǎn)但擁有更多選項的. 你首先將會(huì )用較簡(jiǎn)單的一種去編譯 "Hello, World"程序,就是下面的這個(gè):
public class Hello {
public static void main(String args[]) {
System.out.println("Hello, World");
}
}
要想從Java程序里調用Java編譯器,你需要訪(fǎng)問(wèn)JavaCompiler 接口. 除此外,通過(guò)訪(fǎng)問(wèn)這個(gè)接口,你可以設置源代碼的路徑,classpath,和目標目錄. 通過(guò)指定可編譯的文件為 JavaFileObjectinstance ,你可以將它們全部編譯. 然而,你并不需要對 JavaFileObject 了解多少.
可以使用 ToolProvider 類(lèi)去請求 theJavaCompiler 接口的缺省實(shí)現. 這個(gè) ToolProvider 類(lèi)提供了一個(gè) getSystemJavaCompiler() 方法, 它返回一個(gè) JavaCompiler 接口的實(shí)例.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
使用 JavaCompiler 運行編譯最簡(jiǎn)單的方法是調用在這個(gè)接口工具里定義的 run() 方法,它的實(shí)現是:
int run(InputStream in,
OutputStream out,
OutputStream err,
String... arguments)
分別為前三個(gè)缺省參數 System.in, System.out, and System.err 傳入 null 值. 參數集 String 對象表示著(zhù)傳入編譯器的文件的名稱(chēng).
這樣,你應該像下面這樣去編譯前面顯示的 Hello 源程序:
int results = tool.run(null, null, null, "Hello.java");
假設沒(méi)有編譯錯誤,這樣會(huì )在目標目錄里產(chǎn)生一個(gè) Hello.class文件. 如果這里有錯誤, run() 方法會(huì )把它輸出到標準錯誤輸出流里,也就是 run() 方法的的第三個(gè)參數. 當錯誤發(fā)生時(shí)這個(gè)方法返回一個(gè)非0的結果.
你可以使用下面的代碼去編譯 Hello.java 源文件:
import java.io.*;
import javax.tools.*;
public class CompileIt {
public static void main(String args[]) throws IOException {
JavaCompiler compiler =
ToolProvider.getSystemJavaCompiler();
int results = compiler.run(
null, null, null, "Hello.java");
System.out.println("Result code: " + results);
}
}
一旦你編譯了 CompileIt 一次 ,你就可以多次運行它,當你修改了 Hello.java源程序時(shí)或者要重新編譯它,你不需要重新編譯 CompileIt . 如果沒(méi)有錯誤,運行 CompileIt 會(huì )產(chǎn)生下面的輸出:
> java CompileIt
Result code: 0
運行 CompileIt 同樣也會(huì )在相同的目錄下產(chǎn)生一個(gè) Hello.class 文件:
> ls
CompileIt.class
CompileIt.java
Hello.class
Hello.java
你可以完事了,因為這樣使用標準編譯器已經(jīng)足夠了,可是這還有更有用的. 當你需要更好的處理這些結果時(shí),你可以使用第二種方法來(lái)訪(fǎng)問(wèn)編譯器. 更特別的是,這第二種方式允許開(kāi)發(fā)者將編譯輸出結果用一種更有意義的方式表現出來(lái),而不是簡(jiǎn)單的那種送往stdeer的錯誤文本. 利用 StandardJavaFileManager 類(lèi)我們有這種更好的途徑使用編譯器. 這個(gè)文件管理器提供了一種方式,用來(lái)處理普通文件的輸入輸出操作. 它同時(shí)利用 DiagnosticListener 實(shí)例來(lái)報告調試信息. 你需要使用的 DiagnosticCollector 類(lèi)其實(shí)是監聽(tīng)器的一種實(shí)現.
在搞清楚你需要編譯什么之前,你需要一個(gè)文件管理器. 生成一個(gè)管理器基本上需要兩步: 創(chuàng )建一個(gè) DiagnosticCollector 和 使用 JavaCompiler 的 getStandardFileManager() 方法獲得一個(gè)文件管理器. 把 DiagnosticListener 對象傳入 getStandardFileManager() 方法中. 這個(gè)監聽(tīng)器可以報告一些非致命的問(wèn)題,到后來(lái)你可以選擇性的通過(guò)把它傳入 getTask() 方法來(lái)和編譯器共享.
DiagnosticCollector diagnostics =
new DiagnosticCollector();
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostics, aLocale, aCharset);
你也可以往這個(gè)調用里傳入一個(gè) null 值的診斷器,但這樣也就等于用以前的編譯器方法了.
在詳細查看 StandardJavaFileManager 之前 ,編譯過(guò)程涉及到 JavaCompiler 的一個(gè)方法叫做 getTask(). 它有六個(gè)參數,返回一個(gè)叫做 CompilationTask 內部類(lèi)的實(shí)例:
JavaCompiler.CompilationTask getTask(
Writer out,
JavaFileManager fileManager,
DiagnosticListener<? super JavaFileObject> diagnosticListener,
Iterable options,
Iterable classes,
Iterable<? extends JavaFileObject> compilationUnits)
缺省情況下,大部分它的參數可以是 null.
* out: System.err
* fileManager: compiler‘s standard file manager
* diagnosticListener: compiler‘s default behavior
* options: no command-line options to compiler
* classes: no class names for annotation processing
最后一個(gè)參數 compilationUnits 卻是不能夠為null ,因為它是你要去編譯的東西. 它把我們又帶回了 StandardJavaFileManager類(lèi).注意這個(gè)參數類(lèi)型: Iterable<? extends JavaFileObject>. StandardJavaFileManager 有兩個(gè)方法返回這樣的結果. 你可以使用一個(gè)文件對象的List或者 String 對象的List,用它們來(lái)表示文件名:
Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
Iterable<? extends File> files)
Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(
Iterable names)
并不僅僅 List,實(shí)際上,任何一個(gè)能夠標識需要編譯的內容的集合的 Iterable 都可以. List 出現在這里只是因為它容易生成:
String[] filenames = ...;
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromFiles(Arrays.asList(filenames));
現在你有了編譯源文件的所有的必要的信息. 從 getTask() 返回的 JavaCompiler.CompilationTask 實(shí)現了 Callable.接口 這樣,想讓任務(wù)開(kāi)始就去調用call()方法.
JavaCompiler.CompilationTask task =
compiler.getTask(null, fileManager, null, null, null, compilationUnits);
Boolean success = task.call();
如果沒(méi)有編譯警告和錯誤,這個(gè)call() 方法會(huì )編譯所有的 compilationUnits 變量指定的文件,以及有依賴(lài)關(guān)系的可編譯的文件. 想要知道是否所有的都成功了,去查看一下返回的 Boolean 值. 只有當所有的編譯單元都執行成功了,這個(gè) call() 方法才返回 Boolean.TRUE . 一旦有任何錯誤,這個(gè)方法就會(huì )返回 Boolean.FALSE.
在展示運行這個(gè)例子之前,讓我們添加最后一個(gè)東西,DiagnosticListener, 或者更確切的說(shuō), DiagnosticCollector.的實(shí)現類(lèi).把這個(gè)監聽(tīng)器當作getTask()的第三個(gè)參數傳遞進(jìn)去,你就可以在編譯之后進(jìn)行一些調式信息的查詢(xún)了.
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
System.console().printf(
"Code: %s%n" +
"Kind: %s%n" +
"Position: %s%n" +
"Start Position: %s%n" +
"End Position: %s%n" +
"Source: %s%n" +
"Message: %s%n",
diagnostic.getCode(), diagnostic.getKind(),
diagnostic.getPosition(), diagnostic.getStartPosition(),
diagnostic.getEndPosition(), diagnostic.getSource(),
diagnostic.getMessage(null));
}
在最后,你應該調用管理器的close() 方法.
把所有的放在一起,就得到的了下面的程序,讓我們重新編譯Hello類(lèi).
import java.io.*;
import java.util.*;
import javax.tools.*;
public class BigCompile {
public static void main(String args[]) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector diagnostics =
new DiagnosticCollector();
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(diagnostics, null, null);
Iterable<? extends JavaFileObject> compilationUnits =
fileManager.getJavaFileObjectsFromStrings(Arrays.asList("Hello.java"));
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, null, null, compilationUnits);
Boolean success = task.call();
for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
System.console().printf(
"Code: %s%n" +
"Kind: %s%n" +
"Position: %s%n" +
"Start Position: %s%n" +
"End Position: %s%n" +
"Source: %s%n" +
"Message: %s%n",
diagnostic.getCode(), diagnostic.getKind(),
diagnostic.getPosition(), diagnostic.getStartPosition(),
diagnostic.getEndPosition(), diagnostic.getSource(),
diagnostic.getMessage(null));
}
fileManager.close();
System.out.println("Success: " + success);
}
}
編譯和運行這個(gè)程序會(huì )輸出成功的信息:
> javac BigCompile.java
> java BigCompile
Success: true
然而,如果你把 println 方法改成書(shū)寫(xiě)錯誤的 pritnln 方法,當你運行時(shí)你會(huì )得到下面的信息:
> java BigCompile
Code: compiler.err.cant.resolve.location
Kind: ERROR
Position: 80
Start Position: 70
End Position: 88
Source: Hello.java
Message: Hello.java:3: cannot find symbol
symbol : method pritnln(java.lang.String)
location: class java.io.PrintStream
Success: false
? 2003-2007 JavaEye.com. All rights reserved. [
滬ICP備05023328號 ]