隨筆 - 312, 文章 - 14, 評論 - 1393, 引用 - 0
          數據加載中……

          Java SE6調用Java編譯器的兩種新方法

          本文為原創,如需轉載,請注明作者和出處,謝謝!

          本文曾發表于天極網:http://dev.yesky.com/451/3039451.shtml

          在很多Java應用中需要在程序中調用Java編譯器來編譯和運行。但在早期的版本中(Java SE5及以前版本)中只能通過tools.jar中的com.sun.tools.javac包來調用Java編譯器,但由于tools.jar不是標準的Java庫,在使用時必須要設置這個jar的路徑。而在Java SE6中為我們提供了標準的包來操作Java編譯器,這就是javax.tools包。使用這個包,我們可以不用將jar文件路徑添加到classpath中了。

          一、使用JavaCompiler接口來編譯Java源程序

          使用Java API來編譯Java源程序有很多方法,現在讓我們來看一種最簡單的方法,通過JavaCompiler進行編譯。

          我們可以通過ToolProvider類的靜態方法getSystemJavaCompiler來得到一個JavaCompiler接口的實例。

          JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

          JavaCompiler中最核心的方法是run。通過這個方法可以編譯java源程序。這個方法有3個固定參數和1個可變參數(可變參數是從Jave SE5開始提供的一個新的參數類型,用type… argu表示)。前3個參數分別用來為java編譯器提供參數、得到Java編譯器的輸出信息以及接收編譯器的錯誤信息,后面的可變參數可以傳入一個或多個Java源程序文件。如果run編譯成功,返回0

          int run(InputStream in, OutputStream out, OutputStream err, String... arguments)

          如果前3個參數傳入的是null,那么run方法將以標準的輸入、輸出代替,即System.inSystem.outSystem.err。如果我們要編譯一個test.java文件,并將使用標準輸入輸出,run的使用方法如下:

          int results = tool.run(null, null, null, "test.java");

          下面是使用JavaCompiler的完整代碼。


          import java.io.*;
          import javax.tools.*;

          public class test_compilerapi
          {
              
          public static void main(String args[]) throws IOException
              {
                  JavaCompiler compiler 
          = ToolProvider.getSystemJavaCompiler();
                  
          int results = compiler.run(nullnullnull"test.java");
                  System.out.println((results 
          == 0)?"編譯成功":"編譯失敗");
          // 在程序中運行test
                  Runtime run = Runtime.getRuntime();
                  Process p 
          = run.exec("java test");
                  BufferedInputStream in 
          = new BufferedInputStream(p.getInputStream());
                  BufferedReader br 
          = new BufferedReader(new InputStreamReader(in));
                  String s;
                  
          while ((s = br.readLine()) != null)
                      System.out.println(s);
              }
          }    


          public class test
          {
              
          public static void main(String[] args) throws Exception
              {
                  System.out.println(
          "JavaCompiler測試成功!");
              }
          }

          編譯成功的輸出結果:

          編譯成功

          JavaCompiler測試成功

          編譯失敗的輸出結果:

          test.java:9: 找不到符號

          符號:方法 printlnln(java.lang.String)

          位置: java.io.PrintStream

                        System.out.printlnln("JavaCompiler測試成功!");

                                  ^

          1 錯誤

          編譯失敗

          二、使用StandardJavaFileManager編譯Java源程序

          在第一部分我們討論調用java編譯器的最容易的方法。這種方法可以很好地工作,但它確不能更有效地得到我們所需要的信息,如標準的輸入、輸出信息。而在Java SE6中最好的方法是使用StandardJavaFileManager類。這個類可以很好地控制輸入、輸出,并且可以通過DiagnosticListener得到診斷信息,而DiagnosticCollector類就是listener的實現。

          使用StandardJavaFileManager需要兩步。首先建立一個DiagnosticCollector實例以及通過JavaCompilergetStandardFileManager()方法得到一個StandardFileManager對象。最后通過CompilationTask中的call方法編譯源程序。

          在使用這種方法調用Java編譯時最復雜的方法就是getTask,下面讓我們討論一下getTask方法。這個方法有如下所示的6個參數。


          getTask(Writer out,

          JavaFileManager fileManager,

          DiagnosticListener
          <? super JavaFileObject> diagnosticListener,

          Iterable
          <String> options,

          Iterable
          <String> classes,

          Iterable
          <? extends JavaFileObject> compilationUnits)

          這些參數大多數都可為null。它們的含義所下。

          1.         out::用于輸出錯誤的流,默認是System.err

          2.         fileManager::標準的文件管理。

          3.         diagnosticListener: 編譯器的默認行為。

          4.         options: 編譯器的選項

          5.         classes:參與編譯的class

          最后一個參數compilationUnits不能為null,因為這個對象保存了你想編譯的Java文件。

          在使用完getTask后,需要通過StandardJavaFileManagergetJavaFileObjectsFromFilesgetJavaFileObjectsFromStrings方法得到compilationUnits對象。調用這兩個方法的方式如下:.


          Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
          Iterable
          <? extends File> files)
          Iterable
          <? extends JavaFileObject> getJavaFileObjectsFromStrings(
          Iterable
          <String> names)

          String[] filenames 
          = …;
          Iterable
          <? extends JavaFileObject> compilationUnits =
          fileManager.getJavaFileObjectsFromFiles(Arrays.asList(filenames));

              JavaCompiler.CompilationTask task 
          = compiler.getTask(null, fileManager,
                                  diagnostics, options, 
          null, compilationUnits);

              最后需要關閉fileManager.close();

          下面是一個完整的演示程序。


          import java.io.*;
          import java.util.*;
          import javax.tools.*;

          public class test_compilerapi
          {
              
          private static void compilejava() throws Exception
              {
                  JavaCompiler compiler 
          = ToolProvider.getSystemJavaCompiler();
                  
          // 建立DiagnosticCollector對象
                  DiagnosticCollector<JavaFileObject> diagnostics 
          = new DiagnosticCollector<JavaFileObject>();      
                  StandardJavaFileManager fileManager 
          = compiler.getStandardFileManager(
                                  diagnostics, 
          nullnull);
                  
          // 建立用于保存被編譯文件名的對象
                  
          // 每個文件被保存在一個從JavaFileObject繼承的類中      
                  Iterable<? extends JavaFileObject> compilationUnits = fileManager
                                  .getJavaFileObjectsFromStrings(Arrays asList(
          "test3.java"));
                  JavaCompiler.CompilationTask task 
          = compiler.getTask(null, fileManager,
                                  diagnostics, 
          nullnull, compilationUnits);
                  
          // 編譯源程序
                  boolean success = task.call();
                  fileManager.close();
                  System.out.println((success)
          ?”編譯成功”:”編譯失敗”);
              }
              
          public static void main(String args[]) throws Exception
              {
                  compilejava();
              }
          }

              如果想得到具體的編譯錯誤,可以對Diagnostics進行掃描,代碼如下:


                  for (Diagnostic diagnostic : diagnostics.getDiagnostics())
                      System.out.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));

          被編譯的test.java代碼如下:

          public class test
          {
              
          public static void main(String[] args) throws Exception
              {
                  aa;  
          //錯誤語句
                  System.out.println("JavaCompiler測試成功!");
              }
          }


          在這段代碼中多寫了個aa,得到的編譯錯誤為:

          Code: compiler.err.not.stmt

          Kind: ERROR

          Position: 89

          Start Position: 89

          End Position: 89

          Source: test.java

          Message: test.java:5: 不是語句

          Success: false

              通過JavaCompiler進行編譯都是在當前目錄下生成.class文件,而使用編譯選項可以改變這個默認目錄。編譯選項是一個元素為String類型的Iterable集合。如我們可以使用如下代碼在D盤根目錄下生成.class文件。


          Iterable<String> options = Arrays.asList("-d""d:\\");
          JavaCompiler.CompilationTask task 
          = compiler.getTask(null, fileManager,
                              diagnostics, options, 
          null, compilationUnits);

          在上面的例子中options處的參數為null,而要傳遞編譯器的參數,就需要將options傳入。

          有時我們編譯一個Java源程序文件,而這個源程序文件需要另幾個Java文件,而這些Java文件又在另外一個目錄,那么這就需要為編譯器指定這些文件所在的目錄。

          Iterable<String> options = Arrays.asList("-sourcepath", "d:""src");

          上面的代碼指定的被編譯Java文件所依賴的源文件所在的目錄。

           

          三、在內存中編譯

          JavaCompiler不僅可以編譯硬盤上的Java文件,而且還可以編譯內存中的Java代碼,然后使用reflection來運行它們。我們可以編寫一個JavaSourceFromString類,通過這個類可以輸入Java源代碼。一但建立這個對象,你可以向其中輸入任意的Java代碼,然后編譯和運行,而且無需向硬盤上寫.class文件。

          import java.lang.reflect.*;
          import java.io.*;
          import javax.tools.*;
          import javax.tools.JavaCompiler.CompilationTask;
          import java.util.*;
          import java.net.*;

          public class test_compilerapi
          {
              
          private static void compilerJava() throws Exception
              {
                  JavaCompiler compiler 
          = ToolProvider.getSystemJavaCompiler();
                  DiagnosticCollector
          <JavaFileObject> diagnostics 
          = new DiagnosticCollector<JavaFileObject>();
                  
          // 定義一個StringWriter類,用于寫Java程序
                  StringWriter writer = new StringWriter();
                  PrintWriter out 
          = new PrintWriter(writer);
                  
          // 開始寫Java程序
                  out.println("public class HelloWorld {");
                  out.println(
          " public static void main(String args[]) {");
                  out.println(
          " System.out.println(\"Hello, World\");");
                  out.println(
          " }");
                  out.println(
          "}");
                  out.close();
                  
          //為這段代碼取個名子:HelloWorld,以便以后使用reflection調用
                  JavaFileObject file = new JavaSourceFromString("HelloWorld", writer
                                  .toString());
                  Iterable
          <? extends JavaFileObject> compilationUnits = Arrays
                                  .asList(file);
                  JavaCompiler.CompilationTask task 
          = compiler.getTask(nullnull,
                                  diagnostics, 
          nullnull, compilationUnits);
                  
          boolean success = task.call();
                  System.out.println(
          "Success: " + success);
                  
          // 如果成功,通過reflection執行這段Java程序
                  if (success)
                  {
                      System.out.println(
          "-----輸出-----");
                      Class.forName(
          "HelloWorld").getDeclaredMethod("main"new Class[]
                      { String[].
          class }).invoke(nullnew Object[]
                      { 
          null });
                      System.out.println(
          "-----輸出 -----");
                  }        
              }
              
          public static void main(String args[]) throws Exception
              {
                  compilerJava();
              }
          }
          // 用于傳遞源程序的JavaSourceFromString類
          class JavaSourceFromString extends SimpleJavaFileObject
          {
              
          final String code;
              JavaSourceFromString(String name, String code)
              {
                  
          super(URI.create("string:///" + name.replace('.''/')
                                  
          + Kind.SOURCE.extension), Kind.SOURCE);
                  
          this.code = code;
              }
              @Override
              
          public CharSequence getCharContent(boolean ignoreEncodingErrors)
              {
                  
          return code;
              }
          }






          Android開發完全講義(第2版)(本書版權已輸出到臺灣)

          http://product.dangdang.com/product.aspx?product_id=22741502



          Android高薪之路:Android程序員面試寶典 http://book.360buy.com/10970314.html


          新浪微博:http://t.sina.com.cn/androidguy   昵稱:李寧_Lining

          posted on 2008-05-13 10:25 銀河使者 閱讀(3034) 評論(3)  編輯  收藏 所屬分類: java 原創

          評論

          # re: Java SE6調用Java編譯器的兩種新方法  回復  更多評論   

          沒東西發就別發 發了一年了多了 現在又拿出來重發 搞個毛
          2008-05-13 16:59 | as

          # re: Java SE6調用Java編譯器的兩種新方法[未登錄]  回復  更多評論   

          不同意樓上的觀點,每時每刻大家都在學習中。你學到不代表別人學到了。。呵呵!!話語如有過激,請多原諒
          2008-05-13 19:10 | Samuel

          # re: Java SE6調用Java編譯器的兩種新方法  回復  更多評論   

          最近只是將我在其他媒體發表過的或以前寫過的文章都放到blogjava和我在博客園的blog:nokiaguy.cnblogs.com上。做一個備份和總結,也希望對想學習相關知識的朋友有所幫助。 如果大家有什么意見,盡管提。哈哈!!!
          2008-05-13 19:13 | 銀河使者
          主站蜘蛛池模板: 迁安市| 九寨沟县| 浑源县| 保定市| 肃宁县| 龙岩市| 彰武县| 清原| 枣强县| 利辛县| 南平市| 许昌县| 临西县| 门头沟区| 塘沽区| 裕民县| 离岛区| 哈巴河县| 五台县| 乾安县| 托里县| 宜章县| 漯河市| 长治县| 富平县| 邵阳市| 普陀区| 嘉义县| 思南县| 吉林省| 文化| 荆门市| 眉山市| 忻州市| 黑龙江省| 楚雄市| 通州区| 新营市| 多伦县| 浑源县| 邳州市|