千里冰封
          JAVA 濃香四溢
          posts - 151,comments - 2801,trackbacks - 0

          新 API 功能簡(jiǎn)介

          JDK 6 提供了在運(yùn)行時(shí)調(diào)用編譯器的 API,后面我們將假設(shè)把此 API 應(yīng)用在 JSP 技術(shù)中。在傳統(tǒng)的 JSP 技術(shù)中,服務(wù)器處理 JSP 通常需要進(jìn)行下面 6 個(gè)步驟:

          1. 分析 JSP 代碼;
          2. 生成 Java 代碼;
          3. 將 Java 代碼寫入存儲(chǔ)器;
          4. 啟動(dòng)另外一個(gè)進(jìn)程并運(yùn)行編譯器編譯 Java 代碼;
          5. 將類文件寫入存儲(chǔ)器;
          6. 服務(wù)器讀入類文件并運(yùn)行;

          但 如果采用運(yùn)行時(shí)編譯,可以同時(shí)簡(jiǎn)化步驟 4 和 5,節(jié)約新進(jìn)程的開銷和寫入存儲(chǔ)器的輸出開銷,提高系統(tǒng)效率。實(shí)際上,在 JDK 5 中,Sun 也提供了調(diào)用編譯器的編程接口。然而不同的是,老版本的編程接口并不是標(biāo)準(zhǔn) API 的一部分,而是作為 Sun 的專有實(shí)現(xiàn)提供的,而新版則帶來了標(biāo)準(zhǔn)化的優(yōu)點(diǎn)。

          新 API 的第二個(gè)新特性是可以編譯抽象文件,理論上是任何形式的對(duì)象 —— 只要該對(duì)象實(shí)現(xiàn)了特定的接口。有了這個(gè)特性,上述例子中的步驟 3 也可以省略。整個(gè) JSP 的編譯運(yùn)行在一個(gè)進(jìn)程中完成,同時(shí)消除額外的輸入輸出操作。

          第三個(gè)新特性是可以收集編譯時(shí)的診斷信息。作為對(duì)前兩個(gè)新特性的補(bǔ)充,它可以使開發(fā)人員輕松的輸出必要的編譯錯(cuò)誤或者是警告信息,從而省去了很多重定向的麻煩。





          回頁首


          運(yùn)行時(shí)編譯 Java 文件

          在 JDK 6 中,類庫(kù)通過 javax.tools 包提供了程序運(yùn)行時(shí)調(diào)用編譯器的 API。從這個(gè)包的名字 tools 可以看出,這個(gè)開發(fā)包提供的功能并不僅僅限于編譯器。工具還包括 javah、jar、pack200 等,它們都是 JDK 提供的命令行工具。這個(gè)開發(fā)包希望通過實(shí)現(xiàn)一個(gè)統(tǒng)一的接口,可以在運(yùn)行時(shí)調(diào)用這些工具。在 JDK 6 中,編譯器被給予了特別的重視。針對(duì)編譯器,JDK 設(shè)計(jì)了兩個(gè)接口,分別是 JavaCompilerJavaCompiler.CompilationTask

          下面給出一個(gè)例子,展示如何在運(yùn)行時(shí)調(diào)用編譯器。

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

          通過調(diào)用 ToolProvidergetSystemJavaCompiler 方法,JDK 提供了將當(dāng)前平臺(tái)的編譯器映射到內(nèi)存中的一個(gè)對(duì)象。這樣使用者可以在運(yùn)行時(shí)操縱編譯器。JavaCompiler 是一個(gè)接口,它繼承了 javax.tools.Tool 接口。因此,第三方實(shí)現(xiàn)的編譯器,只要符合規(guī)范就能通過統(tǒng)一的接口調(diào)用。同時(shí),tools 開發(fā)包希望對(duì)所有的工具提供統(tǒng)一的運(yùn)行時(shí)調(diào)用接口。相信將來,ToolProvider 類將會(huì)為更多地工具提供 getSystemXXXTool 方法。tools 開發(fā)包實(shí)際為多種不同工具、不同實(shí)現(xiàn)的共存提供了框架。

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

          獲得編譯器對(duì)象之后,可以調(diào)用 Tool.run 方法對(duì)源文件進(jìn)行編譯。Run 方法的前三個(gè)參數(shù),分別可以用來重定向標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出,null 值表示使用默認(rèn)值。清單 1 給出了一個(gè)完整的例子:

          清單 1. 程序運(yùn)行時(shí)編譯文件
          01 package compile;
          02 import java.util.Date;
          03 public class Target {
          04   public void doSomething(){
          05     Date date = new Date(1033); 
                   
          // 這個(gè)構(gòu)造函數(shù)被標(biāo)記為deprecated, 編譯時(shí)會(huì)
                   
          // 向錯(cuò)誤輸出輸出信息。
          06     System.out.println("Doing");
          07   }
          08 }

          09 package compile;
          10 import javax.tools.*;
          11 import java.io.FileOutputStream;
          12 public class Compiler {
          13   public static void main(String[] args) throws Exception{
          14     String fullQuanlifiedFileName = "compile" + java.io.File.separator +
                       
          "Target.java";     
          15     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

          16     FileOutputStream err = new FileOutputStream("err.txt");

          17     int compilationResult = compiler.run(nullnull, err, fullQuanlifiedFileName);

          18     if(compilationResult == 0){
          19       System.out.println("Done");
          20     } else {
          21       System.out.println("Fail");
          22     }
          23   }
          24 }
            

          首先運(yùn)行 <JDK60_INSTALLATION_DIR>\bin\javac Compiler.java,然后運(yùn)行 <JDK60_INSTALLATION_DIR>\jdk1.6.0\bin\java compile.Compiler。屏幕上將輸出 Done ,并會(huì)在當(dāng)前目錄生成一個(gè) err.txt 文件,文件內(nèi)容如下:

          Note: compile/Target.java uses or overrides a deprecated API.
          Note: Recompile with 
          -Xlint:deprecation for details.

          仔細(xì)觀察 run 方法,可以發(fā)現(xiàn)最后一個(gè)參數(shù)是 String...arguments,是一個(gè)變長(zhǎng)的字符串?dāng)?shù)組。它的實(shí)際作用是接受傳遞給 javac 的參數(shù)。假設(shè)要編譯 Target.java 文件,并顯示編譯過程中的詳細(xì)信息。命令行為:javac Target.java -verbose。相應(yīng)的可以將 17 句改為:

          int compilationResult = compiler.run(nullnull, err, “-verbose”,fullQuanlifiedFileName);

          編譯非文本形式的文件

          JDK 6 的編譯器 API 的另外一個(gè)強(qiáng)大之處在于,它可以編譯的源文件的形式并不局限于文本文件。JavaCompiler 類依靠文件管理服務(wù)可以編譯多種形式的源文件。比如直接由內(nèi)存中的字符串構(gòu)造的文件,或者是從數(shù)據(jù)庫(kù)中取出的文件。這種服務(wù)是由 JavaFileManager 類提供的。通常的編譯過程分為以下幾個(gè)步驟:

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

          在這個(gè)過程中,JavaFileManager 類可以起到創(chuàng)建輸出文件,讀入并緩存輸出文件的作用。由于它可以讀入并緩存輸入文件,這就使得讀入各種形式的輸入文件成為可能。JDK 提供的命令行工具,處理機(jī)制也大致相似,在未來的版本中,其它的工具處理各種形式的源文件也成為可能。為此,新的 JDK 定義了 javax.tools.FileObjectjavax.tools.JavaFileObject 接口。任何類,只要實(shí)現(xiàn)了這個(gè)接口,就可以被 JavaFileManager 識(shí)別。

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

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

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


          圖 1. 使用 CompilationTask 進(jìn)行編譯
          使用 CompilationTask 進(jìn)行編譯

          下面的例子通過構(gòu)造 CompilationTask 分多步編譯一組 Java 源文件。


          清單 2. 構(gòu)造 CompilationTask 進(jìn)行編譯
          01 package math;

          02 public class Calculator {
          03     public int multiply(int multiplicand, int multiplier) {
          04         return multiplicand * multiplier;
          05     }
          06 }

          07 package compile;
          08 import javax.tools.*;
          09 import java.io.FileOutputStream;
          10 import java.util.Arrays;
          11 public class Compiler {
          12   public static void main(String[] args) throws Exception{
          13     String fullQuanlifiedFileName = "math" + java.io.File.separator +"Calculator.java";
          14     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
          15     StandardJavaFileManager fileManager  =
                     compiler.getStandardFileManager(
          nullnullnull);

          16     Iterable<? extends JavaFileObject> files =
                       fileManager.getJavaFileObjectsFromStrings(
                       Arrays.asList(fullQuanlifiedFileName));
          17     JavaCompiler.CompilationTask task = compiler.getTask(
                       
          null, fileManager, nullnullnull, files);

          18     Boolean result = task.call();
          19     if( result == true ) {
          20       System.out.println("Succeeded");
          21     }
          22   }
          23 }

          以上是第一步,通過構(gòu)造一個(gè) CompilationTask 編譯了一個(gè) Java 文件。14-17 行實(shí)現(xiàn)了主要邏輯。第 14 行,首先取得一個(gè)編譯器對(duì)象。由于僅僅需要編譯普通文件,因此第 15 行中通過編譯器對(duì)象取得了一個(gè)標(biāo)準(zhǔn)文件管理器。16 行,將需要編譯的文件構(gòu)造成了一個(gè) Iterable 對(duì)象。最后將文件管理器和 Iterable 對(duì)象傳遞給 JavaCompilergetTask 方法,取得了 JavaCompiler.CompilationTask 對(duì)象。

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


          清單 3. 定制 JavaFileObject 對(duì)象
          01 package math;
          02 import java.net.URI;
          03 public class StringObject extends SimpleJavaFileObject{
          04     private String contents = null;
          05     public StringObject(String className, String contents) throws Exception{
          06         super(new URI(className), Kind.SOURCE);
          07         this.contents = contents;
          08     }

          09     public CharSequence getCharContent(boolean ignoreEncodingErrors) 
                       
          throws IOException {
          10         return contents;
          11     }
          12 }

          SimpleJavaFileObjectJavaFileObject 的子類,它提供了默認(rèn)的實(shí)現(xiàn)。繼承 SimpleJavaObject 之后,只需要實(shí)現(xiàn) getCharContent 方法。如 清單 3 中的 9-11 行所示。接下來,在內(nèi)存中構(gòu)造 Calculator 的測(cè)試類 CalculatorTest,并將代表該類的字符串放置到 StringObject 中,傳遞給 JavaCompilergetTask 方法。清單 4 展現(xiàn)了這些步驟。


          清單 4. 編譯非文本形式的源文件
          01 package math;
          02 import javax.tools.*;
          03 import java.io.FileOutputStream;
          04 import java.util.Arrays;
          05 public class AdvancedCompiler {
          06   public static void main(String[] args) throws Exception{

          07     // Steps used to compile Calculator
          08     // Steps used to compile StringObject

          09     // construct CalculatorTest in memory
          10     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
          11     StandardJavaFileManager fileManager  =
                     compiler.getStandardFileManager(
          nullnullnull);
          12         JavaFileObject file = constructTestor();
          13         Iterable<? extends JavaFileObject> files = Arrays.asList(file);
          14         JavaCompiler.CompilationTask task = compiler.getTask (
                           
          null, fileManager, nullnullnull, files);

          15         Boolean result = task.call();
          16         if( result == true ) {
          17           System.out.println("Succeeded");
          18         }
          19   }

          20   private static SimpleJavaFileObject constructTestor() {
          21     StringBuilder contents = new StringBuilder(
                     
          "package math;" +
                     
          "class CalculatorTest {\n" +
                       
          "  public void testMultiply() {\n" +
                     
          "    Calculator c = new Calculator();\n" +
                     
          "    System.out.println(c.multiply(2, 4));\n" +
                     
          "  }\n" +
                     
          "  public static void main(String[] args) {\n" +
                     
          "    CalculatorTest ct = new CalculatorTest();\n" +
                     
          "    ct.testMultiply();\n" +
                     
          "  }\n" +
                     
          "}\n");
          22      StringObject so = null;
          23      try {
          24        so = new StringObject("math.CalculatorTest", contents.toString());
          25      } catch(Exception exception) {
          26        exception.printStackTrace();
          27      }
          28      return so;
          29    }
          30 }

          實(shí)現(xiàn)邏輯和 清單 2 相似。不同的是在 20-30 行,程序在內(nèi)存中構(gòu)造了 CalculatorTest 類,并且通過 StringObject 的構(gòu)造函數(shù),將內(nèi)存中的字符串,轉(zhuǎn)換成了 JavaFileObject 對(duì)象。





          回頁首


          采集編譯器的診斷信息

          第三個(gè)新增加的功能,是收集編譯過程中的診斷信息。診斷信息,通常指錯(cuò)誤、警告或是編譯過程中的詳盡輸出。JDK 6 通過 Listener 機(jī)制,獲取這些信息。如果要注冊(cè)一個(gè) DiagnosticListener,必須使用 CompilationTask 來進(jìn)行編譯,因?yàn)?Tool 的 run 方法沒有辦法注冊(cè) Listener。步驟很簡(jiǎn)單,先構(gòu)造一個(gè) Listener,然后傳遞給 JavaFileManager 的構(gòu)造函數(shù)。清單 5 對(duì) 清單 2 進(jìn)行了改動(dòng),展示了如何注冊(cè)一個(gè) DiagnosticListener


          清單 5. 注冊(cè)一個(gè) DiagnosticListener 收集編譯信息

          01 package math;

          02 public class Calculator {
          03   public int multiply(int multiplicand, int multiplier) {
          04     return multiplicand * multiplier 
                   
          // deliberately omit semicolon, ADiagnosticListener 
                   
          // will take effect
          05   }
          06 }

          07 package compile;
          08 import javax.tools.*;
          09 import java.io.FileOutputStream;
          10 import java.util.Arrays;
          11 public class CompilerWithListener {
          12   public static void main(String[] args) throws Exception{
          13     String fullQuanlifiedFileName = "math" + 
                     java.io.File.separator 
          +"Calculator.java";
          14     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
          15     StandardJavaFileManager fileManager  =
                     compiler.getStandardFileManager(
          nullnullnull);

          16     Iterable<? extends JavaFileObject> files =
                     fileManager.getJavaFileObjectsFromStrings(
                     Arrays.asList(fullQuanlifiedFileName));
          17       DiagnosticCollector<JavaFileObject> collector =
                     
          new DiagnosticCollector<JavaFileObject>(); 
          18       JavaCompiler.CompilationTask task = 
                     compiler.getTask(
          null, fileManager, collector, nullnull, files);

          19     Boolean result = task.call();
          20     List<Diagnostic<? extends JavaFileObject>> diagnostics = 
                     collector.getDiagnostics();
          21     for(Diagnostic<? extends JavaFileObject> d : diagnostics){
          22         System.out.println("Line Number->" + d.getLineNumber());
          23           System.out.println("Message->"+ 
                             d.getMessage(Locale.ENGLISH));
          24           System.out.println("Source" + d.getCode());
          25           System.out.println("\n");
          26     }

          27     if( result == true ) {
          28       System.out.println("Succeeded");
          29     }
          30   }
          31 }


          在 17 行,構(gòu)造了一個(gè) DiagnosticCollector 對(duì)象,這個(gè)對(duì)象由 JDK 提供,它實(shí)現(xiàn)了 DiagnosticListener 接口。18 行將它注冊(cè)到 CompilationTask 中去。一個(gè)編譯過程可能有多個(gè)診斷信息。每一個(gè)診斷信息,被抽象為一個(gè) Diagnostic。20-26 行,將所有的診斷信息逐個(gè)輸出。編譯并運(yùn)行 Compiler,得到以下輸出:


          清單 6. DiagnosticCollector 收集的編譯信息
          Line Number->5
          Message
          ->math/Calculator.java:5';' expected
          Source
          ->compiler.err.expected

          實(shí)際上,也可以由用戶自己定制。
          清單 7 給出了一個(gè)定制的 Listener
          清單 7. 自定義的 DiagnosticListener

          01 class ADiagnosticListener implements DiagnosticListener<JavaFileObject>{
          02      public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
          03       System.out.println("Line Number->" + diagnostic.getLineNumber());
          04       System.out.println("Message->"+ diagnostic.getMessage(Locale.ENGLISH));
          05       System.out.println("Source" + diagnostic.getCode());
          06       System.out.println("\n");
          07     }
          08 }

          總結(jié)

          JDK 6 的編譯器新特性,使得開發(fā)者可以更自如的控制編譯的過程,這給了工具開發(fā)者更加靈活的自由度。通過 API 的調(diào)用完成編譯操作的特性,使得開發(fā)者可以更方便、高效地將編譯變?yōu)檐浖到y(tǒng)運(yùn)行時(shí)的服務(wù)。而編譯更廣泛形式的源代碼,則為整合更多的數(shù)據(jù)源及功能提供了 強(qiáng)大的支持。相信隨著 JDK 的不斷完善,更多的工具將具有 API 支持,我們拭目以待。







          盡管千里冰封
          依然擁有晴空

          你我共同品味JAVA的濃香.
          posted on 2007-11-15 08:53 千里冰封 閱讀(1657) 評(píng)論(0)  編輯  收藏 所屬分類: 轉(zhuǎn)載文章
          主站蜘蛛池模板: 吉木乃县| 健康| 万盛区| 滨州市| 安康市| 南通市| 额济纳旗| 子洲县| 黄浦区| 平远县| 大厂| 宁德市| 拜泉县| 贡嘎县| 盐亭县| 文化| 哈尔滨市| 临湘市| 荔浦县| 诸城市| 成武县| 鹤壁市| 安平县| 辽宁省| 昭平县| 慈溪市| 深水埗区| 天津市| 岳阳县| 洪洞县| 崇左市| 大兴区| 郎溪县| 同仁县| 齐齐哈尔市| 连平县| 綦江县| 三江| 明光市| 玉山县| 大新县|