熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> JSP教程 >> 正文

Java SE 6 新特性: 編譯器 API

2013-11-15 11:46:44  來源: JSP教程 

   年底Sun 公司發布了 Java Standard Edition (Java SE )的最終正式版代號 Mustang(野馬)跟 Tiger(Java SE )相比Mustang 在性能方面有了不錯的提升與 Tiger 在 API 庫方面的大幅度加強相比雖然 Mustang 在 API 庫方面的新特性顯得不太多但是也提供了許多實用和方便的功能在腳本WebServiceXML編譯器 API數據庫JMX網絡和 Instrumentation 方面都有不錯的新特性和功能加強 本系列 文章主要介紹 Java SE 在 API 庫方面的部分新特性通過一些例子和講解幫助開發者在編程實踐當中更好的運用 Java SE 提高開發效率

  本文是其中的第四篇介紹了 JDK 中為在運行時操縱編譯器所增加的編譯器 API(JSR 您將了解到利用此 API 開發人員可以在運行時調用 Java 編譯器還可以編譯非文本形式的 Java 源代碼最後還能夠采集編譯器的診斷信息本文將展開描述這些功能並使用這些功能構造一個簡單的應用 —— 在內存中直接為一個類生成測試用例

  新API 功能簡介

  JDK 提供了在運行時調用編譯器的 API後面我們將假設把此 API 應用在 JSP 技術中在傳統的 JSP 技術中服務器處理 JSP 通常需要進行下面 個步驟


    分析 JSP 代碼
    生成 Java 代碼
    將 Java 代碼寫入存儲器
    啟動另外一個進程並運行編譯器編譯 Java 代碼
    將類文件寫入存儲器
    服務器讀入類文件並運行

  但如果采用運行時編譯可以同時簡化步驟 節約新進程的開銷和寫入存儲器的輸出開銷提高系統效率實際上在 JDK Sun 也提供了調用編譯器的編程接口然而不同的是老版本的編程接口並不是標准 API 的一部分而是作為 Sun 的專有實現提供的而新版則帶來了標准化的優點

  新API 的第二個新特性是可以編譯抽象文件理論上是任何形式的對象 —— 只要該對象實現了特定的接口有了這個特性上述例子中的步驟 也可以省略整個 JSP 的編譯運行在一個進程中完成同時消除額外的輸入輸出操作

  第三個新特性是可以收集編譯時的診斷信息作為對前兩個新特性的補充它可以使開發人員輕松的輸出必要的編譯錯誤或者是警告信息從而省去了很多重定向的麻煩

  運行時編譯 Java 文件

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

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


指定編譯文件名稱(該文件必須在 CLASSPATH 中可以找到)String fullQuanlifiedFileName = compile + javaioFileseparator +Targetjava;
獲得編譯器對象 JavaCompiler compiler = ToolProvidergetSystemJavaCompiler();

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


編譯文件int result = compilerrun(null null null fileToCompile);

  獲得編譯器對象之後可以調用 Toolrun 方法對源文件進行編譯Run 方法的前三個參數分別可以用來重定向標准輸入標准輸出和標准錯誤輸出null 值表示使用默認值清單 給出了一個完整的例子

  清單 程序運行時編譯文件

   package compile;
import javautilDate;
public class Target {
public void doSomething(){
Date date = new Date( );
// 這個構造函數被標記為deprecated 編譯時會
// 向錯誤輸出輸出信息
Systemoutprintln(Doing);
}
}

package compile;
import javaxtools*;
import javaioFileOutputStream;
public class Compiler {
public static void main(String[] args) throws Exception{
String fullQuanlifiedFileName = compile + javaioFileseparator +
Targetjava;
JavaCompiler compiler = ToolProvidergetSystemJavaCompiler();

FileOutputStream err = new FileOutputStream(errtxt);

int compilationResult = compilerrun(null null err fullQuanlifiedFileName);

if(compilationResult == ){
Systemoutprintln(Done);
} else {
Systemoutprintln(Fail);
}
}
}


  首 先運行 <JDK_INSTALLATION_DIR>\bin\javac Compilerjava然後運行 <JDK_INSTALLATION_DIR>\jdk\bin\java compileCompiler屏幕上將輸出 Done 並會在當前目錄生成一個 errtxt 文件文件內容如下

  

  Note: compile/Targetjava uses or overrides a deprecated API
Note: Recompile with Xlint:deprecation for details


  仔細觀察 run 方法可以發現最後一個參數是 Stringarguments是一個變長的字符串數組它的實際作用是接受傳遞給 javac 的參數假設要編譯 Targetjava 文件並顯示編譯過程中的詳細信息命令行為javac Targetjava verbose相應的可以將 句改為

  

  int compilationResult = compilerrun(null null err verbosefullQuanlifiedFileName);

    編譯非文本形式的文件

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


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

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

  如果要使用 JavaFileManager就必須構造 CompilationTaskJDK 提供了 JavaCompilerCompilationTask 類來封裝一個編譯操作這個類可以通過

  

  JavaCompilergetTask (
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 的處理器 展示了通過 CompilationTask 進行編譯的過程

  圖 使用 CompilationTask 進行編譯

  使用 CompilationTask 進行編譯

  下面的例子通過構造 CompilationTask 分多步編譯一組 Java 源文件

  清單 構造 CompilationTask 進行編譯

   package math;

public class Calculator {
public int multiply(int multiplicand int multiplier) {
return multiplicand * multiplier;
}
}

package compile;
import javaxtools*;
import javaioFileOutputStream;
import javautilArrays;
public class Compiler {
public static void main(String[] args) throws Exception{
String fullQuanlifiedFileName = math + javaioFileseparator +Calculatorjava;
JavaCompiler compiler = ToolProvidergetSystemJavaCompiler();
StandardJavaFileManager fileManager =
compilergetStandardFileManager(null null null);

Iterable<? extends JavaFileObject> files =
fileManagergetJavaFileObjectsFromStrings(
ArraysasList(fullQuanlifiedFileName));
JavaCompilerCompilationTask task = compilergetTask(
null fileManager null null null files);

Boolean result = taskcall();
if( result == true ) {
Systemoutprintln(Succeeded);
}
}
}


  以上是第一步通過構造一個 CompilationTask 編譯了一個 Java 文件 行實現了主要邏輯首先取得一個編譯器對象由於僅僅需要編譯普通文件因此第 行中通過編譯器對象取得了一個標准文件管理器將需要編譯的文件構造成了一個 Iterable 對象最後將文件管理器和 Iterable 對象傳遞給 JavaCompiler 的 getTask 方法取得了 JavaCompilerCompilationTask 對象

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

  清單 定制 JavaFileObject 對象

   package math;
import URI;
public class StringObject extends SimpleJavaFileObject{
private String contents = null;
public StringObject(String className String contents) throws Exception{
super(new URI(className) KindSOURCE);
ntents = contents;
}

public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
return contents;
}
}


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

  清單 編譯非文本形式的源文件

   package math;
import javaxtools*;
import javaioFileOutputStream;
import javautilArrays;
public class AdvancedCompiler {
public static void main(String[] args) throws Exception{

// Steps used to compile Calculator
// Steps used to compile StringObject

// construct CalculatorTest in memory
JavaCompiler compiler = ToolProvidergetSystemJavaCompiler();
StandardJavaFileManager fileManager =
compilergetStandardFileManager(null null null);
JavaFileObject file = constructTestor();
Iterable<? extends JavaFileObject> files = ArraysasList(file);
JavaCompilerCompilationTask task = compilergetTask (
null fileManager null null null files);

Boolean result = taskcall();
if( result == true ) {
Systemoutprintln(Succeeded);
}
}

private static SimpleJavaFileObject constructTestor() {
StringBuilder contents = new StringBuilder(
package math; +
class CalculatorTest {\n +
public void testMultiply() {\n +
Calculator c = new Calculator();\n +
Systemoutprintln(cmultiply( ));\n +
}\n +
public static void main(String[] args) {\n +
CalculatorTest ct = new CalculatorTest();\n +
cttestMultiply();\n +
}\n +
}\n);
StringObject so = null;
try {
so = new StringObject(mathCalculatorTest contentstoString());
} catch(Exception exception) {
exceptionprintStackTrace();
}
return so;
}
}


  實現邏輯和 清單 相似不同的是在 程序在內存中構造了 CalculatorTest 類並且通過 StringObject 的構造函數將內存中的字符串轉換成了 JavaFileObject 對象

  采集編譯器的診斷信息

  第三個新增加的功能是收集編譯過程中的診斷信息診斷信息通常指錯誤警告或是編譯過程中的詳盡輸出JDK 通過 Listener 機制獲取這些信息如果要注冊一個 DiagnosticListener必須使用 CompilationTask 來進行編譯因為 Tool 的 run 方法沒有辦法注冊 Listener步驟很簡單先構造一個 Listener然後傳遞給 JavaFileManager 的構造函數清單 對 清單 進行了改動展示了如何注冊一個 DiagnosticListener

  清單 注冊一個 DiagnosticListener 收集編譯信息

   package math;

public class Calculator {
public int multiply(int multiplicand int multiplier) {
return multiplicand * multiplier
// deliberately omit semicolon ADiagnosticListener
// will take effect
}
}

package compile;
import javaxtools*;
import javaioFileOutputStream;
import javautilArrays;
public class CompilerWithListener {
public static void main(String[] args) throws Exception{
String fullQuanlifiedFileName = math +
javaioFileseparator +Calculatorjava;
JavaCompiler compiler = ToolProvidergetSystemJavaCompiler();
StandardJavaFileManager fileManager =
compilergetStandardFileManager(null null null);

Iterable<? extends JavaFileObject> files =
fileManagergetJavaFileObjectsFromStrings(
ArraysasList(fullQuanlifiedFileName));
DiagnosticCollector<JavaFileObject> collector =
new DiagnosticCollector<JavaFileObject>();
JavaCompilerCompilationTask task =
compilergetTask(null fileManager collector null null files);

Boolean result = taskcall();
List<Diagnostic<? extends JavaFileObject>> diagnostics =
collectorgetDiagnostics();
for(Diagnostic<? extends JavaFileObject> d : diagnostics){
Systemoutprintln(Line Number> + dgetLineNumber());
Systemoutprintln(Message>+
dgetMessage(LocaleENGLISH));
Systemoutprintln(Source + dgetCode());
Systemoutprintln(\n);
}

if( result == true ) {
Systemoutprintln(Succeeded);
}
}
}


  在 構造了一個 DiagnosticCollector 對象這個對象由 JDK 提供它實現了 DiagnosticListener 接口 行將它注冊到 CompilationTask 中去一個編譯過程可能有多個診斷信息每一個診斷信息被抽象為一個 Diagnostic將所有的診斷信息逐個輸出編譯並運行 Compiler得到以下輸出

  清單 DiagnosticCollector 收集的編譯信息

  Line Number>
Message>math/Calculatorjava:: ; expected
Source>compilererrexpected


  實際上也可以由用戶自己定制清單 給出了一個定制的 Listener

  清單 自定義的 DiagnosticListener

   class ADiagnosticListener implements DiagnosticListener<JavaFileObject>{
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
Systemoutprintln(Line Number> + diagnosticgetLineNumber());
Systemoutprintln(Message>+ diagnosticgetMessage(LocaleENGLISH));
Systemoutprintln(Source + diagnosticgetCode());
Systemoutprintln(\n);
}
}

  總結

  JDK 的編譯器新特性使得開發者可以更自如的控制編譯的過程這給了工具開發者更加靈活的自由度通過 API 的調用完成編譯操作的特性使得開發者可以更方便高效地將編譯變為軟件系統運行時的服務而編譯更廣泛形式的源代碼則為整合更多的數據源及功能提供了 強大的支持相信隨著 JDK 的不斷完善更多的工具將具有 API 支持我們拭目以待


From:http://tw.wingwit.com/Article/program/Java/JSP/201311/19606.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.