`
lt200819
  • 浏览: 182485 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Compiler API

    博客分类:
  • JAVA
 
阅读更多
JDK 6 提供了在运行时调用编译器的 API,后面我们将假设把此 API 应用在 JSP 技术中。在传统的 JSP 技术中,服务器处理 JSP 通常需要进行下面 6 个步骤:
分析 JSP 代码;
生成 Java 代码;
将 Java 代码写入存储器;
启动另外一个进程并运行编译器编译 Java 代码;
将类文件写入存储器;
服务器读入类文件并运行;
但如果采用运行时编译,可以同时简化步骤 4 和 5,节约新进程的开销和写入存储器的输出开销,提高系统效率。实际上,在 JDK 5 中,Sun 也提供了调用编译器的编程接口。然而不同的是,老版本的编程接口并不是标准 API 的一部分,而是作为 Sun 的专有实现提供的,而新版则带来了标准化的优点。
新 API 的第二个新特性是可以编译抽象文件,理论上是任何形式的对象 —— 只要该对象实现了特定的接口。有了这个特性,上述例子中的步骤 3 也可以省略。整个 JSP 的编译运行在一个进程中完成,同时消除额外的输入输出操作。
第三个新特性是可以收集编译时的诊断信息。作为对前两个新特性的补充,它可以使开发人员轻松的输出必要的编译错误或者是警告信息,从而省去了很多重定向的麻烦。
回页首
运行时编译 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 值表示使用默认值。清单 1 给出了一个完整的例子:

清单 1. 程序运行时编译文件
01 package compile;
02 import java.util.Date;
03 public class Target {
04   public void doSomething(){
05     Date date = new Date(10, 3, 3);
         // 这个构造函数被标记为deprecated, 编译时会
         // 向错误输出输出信息。
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(null, null, err, fullQuanlifiedFileName);

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

首先运行 <JDK60_INSTALLATION_DIR>\bin\javac Compiler.java,然后运行 <JDK60_INSTALLATION_DIR>\jdk1.6.0\bin\java compile.Compiler。屏幕上将输出 Done ,并会在当前目录生成一个 err.txt 文件,文件内容如下:
Note: compile/Target.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

仔细观察 run 方法,可以发现最后一个参数是 String...arguments,是一个变长的字符串数组。它的实际作用是接受传递给 javac 的参数。假设要编译 Target.java 文件,并显示编译过程中的详细信息。命令行为:javac Target.java -verbose。相应的可以将 17 句改为:
int compilationResult = compiler.run(null, null, err, “-verbose”,fullQuanlifiedFileName);

回页首
编译非文本形式的文件
JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件。JavaCompiler 类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager 类提供的。通常的编译过程分为以下几个步骤:
解析 javac 的参数;
在 source path 和/或 CLASSPATH 中查找源文件或者 jar 包;
处理输入,输出文件;
在这个过程中,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 进行编译的过程:

图 1. 使用 CompilationTask 进行编译

下面的例子通过构造 CompilationTask 分多步编译一组 Java 源文件。

清单 2. 构造 CompilationTask 进行编译
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(null, null, null);

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

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

以上是第一步,通过构造一个 CompilationTask 编译了一个 Java 文件。14-17 行实现了主要逻辑。第 14 行,首先取得一个编译器对象。由于仅仅需要编译普通文件,因此第 15 行中通过编译器对象取得了一个标准文件管理器。16 行,将需要编译的文件构造成了一个 Iterable 对象。最后将文件管理器和 Iterable 对象传递给 JavaCompiler 的 getTask 方法,取得了 JavaCompiler.CompilationTask 对象。
接下来第二步,开发者希望生成 Calculator 的一个测试类,而不是手工编写。使用 compiler API,可以将内存中的一段字符串,编译成一个 CLASS 文件。

清单 3. 定制 JavaFileObject 对象
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 }
  

SimpleJavaFileObject 是 JavaFileObject 的子类,它提供了默认的实现。继承 SimpleJavaObject 之后,只需要实现 getCharContent 方法。如 清单 3 中的 9-11 行所示。接下来,在内存中构造 Calculator 的测试类 CalculatorTest,并将代表该类的字符串放置到 StringObject 中,传递给 JavaCompiler 的 getTask 方法。清单 4 展现了这些步骤。

清单 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(null, null, null);
12         JavaFileObject file = constructTestor();
13         Iterable<? extends JavaFileObject> files = Arrays.asList(file);
14         JavaCompiler.CompilationTask task = compiler.getTask (
                 null, fileManager, null, null, null, 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 }

实现逻辑和 清单 2 相似。不同的是在 20-30 行,程序在内存中构造了 CalculatorTest 类,并且通过 StringObject 的构造函数,将内存中的字符串,转换成了 JavaFileObject 对象。
回页首
采集编译器的诊断信息
第三个新增加的功能,是收集编译过程中的诊断信息。诊断信息,通常指错误、警告或是编译过程中的详尽输出。JDK 6 通过 Listener 机制,获取这些信息。如果要注册一个 DiagnosticListener,必须使用 CompilationTask 来进行编译,因为 Tool 的 run 方法没有办法注册 Listener。步骤很简单,先构造一个 Listener,然后传递给 JavaFileManager 的构造函数。清单 5 对 清单 2 进行了改动,展示了如何注册一个 DiagnosticListener。

清单 5. 注册一个 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(null, null, null);

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, null, null, 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 行,构造了一个 DiagnosticCollector 对象,这个对象由 JDK 提供,它实现了 DiagnosticListener 接口。18 行将它注册到 CompilationTask 中去。一个编译过程可能有多个诊断信息。每一个诊断信息,被抽象为一个 Diagnostic。20-26 行,将所有的诊断信息逐个输出。编译并运行 Compiler,得到以下输出:

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

实际上,也可以由用户自己定制。清单 7 给出了一个定制的 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 }

回页首
总结
JDK 6 的编译器新特性,使得开发者可以更自如的控制编译的过程,这给了工具开发者更加灵活的自由度。通过 API 的调用完成编译操作的特性,使得开发者可以更方便、高效地将编译变为软件系统运行时的服务。而编译更广泛形式的源代码,则为整合更多的数据源及功能提供了强大的支持。相信随着 JDK 的不断完善,更多的工具将具有 API 支持,我们拭目以待。
分享到:
评论

相关推荐

    .NET Development Using the Compiler API

    This is the first book to describe the recent significant changes to the .NET compilation process and demonstrate how .NET developers can use the new Compiler API to create compelling applications....

    .NET Development Using the Compiler API(Apress,2016)

    This is the first book to describe the recent significant changes to the .NET compilation process and demonstrate how .NET developers can use the new Compiler API to create compelling applications....

    Flex3 compiler API (En)

    The Flex compiler API lets you compile Flex applications from Java applications.

    JavaCompiler --JDK6 API的简介(java动态编译)

    java动态编译的几种技术介绍。JavaCompiler --JDK6 API的简介(java动态编译)

    JDK1.6新特性与实战例子

    JDK6的新特性之一:...现在我们可以用JDK6 的Compiler API(JSR 199)去动态编译Java源文件 JDK6的新特性之五:轻量级Http Server JDK6提供了一个简单的Http Server API,据此我们可以构建自己的嵌入式Http Server,

    Api-api-development-tools.zip

    Api-api-development-tools.zip,用于构建restful http json api.httpapi开发工具的有用资源集合,一个api可以被认为是多个软件设备之间通信的指导手册。例如,api可用于web应用程序之间的数据库通信。通过提取实现并...

    scala-compiler-2.12.7-API文档-中文版.zip

    包含翻译后的API文档:scala-compiler-2.12.7-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.scala-lang:scala-compiler:2.12.7; 标签:scala、lang、compiler、中文文档、jar包、java; 使用方法:解压翻译...

    JDK-API-1-6-zzh-CN帮助文档中文版

    Java SE 6包括许多新的特性和改进,包括JDBC 4.0 API,Java Compiler API,Pluggable Annotation Processing API和新的XML和Web服务API等。此外,Java SE 6还包括对桌面应用程序的增强支持,如Swing,Java2D和Java ...

    commons-compiler-3.0.8-API文档-中文版.zip

    包含翻译后的API文档:commons-compiler-3.0.8-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.codehaus.janino:commons-compiler:3.0.8; 标签:codehaus、compiler、commons、janino、jar包、java、中文文档...

    JDK+6.0+ZH+API.chm.zip

    JDK6的新特性之四_使用Compiler API JDK6的新特性之五_轻量级HttpServer JDK6的新特性之七_用Console开发控制台程序 JDK6的新特性之八_嵌入式数据库Derby JDK6的新特性之六_插入式注解处理API JDK6的新特性...

    compiler-0.9.3-API文档-中文版.zip

    包含翻译后的API文档:compiler-0.9.3-javadoc-API文档-中文(简体)版.zip; Maven坐标:com.github.spullara.mustache.java:compiler:0.9.3; 标签:github、java、spullara、compiler、mustache、jar包、java、中文...

    commons-compiler-3.0.8-API文档-中英对照版.zip

    包含翻译后的API文档:commons-compiler-3.0.8-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.codehaus.janino:commons-compiler:3.0.8; 标签:codehaus、compiler、commons、janino、jar包、java...

    scala-compiler-2.11.8-API文档-中英对照版.zip

    包含翻译后的API文档:scala-compiler-2.11.8-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.scala-lang:scala-compiler:2.11.8; 标签:scala、lang、compiler、中英对照文档、jar包、java; 使用...

    commons-compiler-2.7.6-API文档-中文版.zip

    包含翻译后的API文档:commons-compiler-2.7.6-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.codehaus.janino:commons-compiler:2.7.6; 标签:codehaus、compiler、commons、janino、jar包、java、API文档、...

    scala-compiler-2.11.0-API文档-中英对照版.zip

    包含翻译后的API文档:scala-compiler-2.11.0-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:org.scala-lang:scala-compiler:2.11.0; 标签:scala、lang、compiler、jar包、java、API文档、中英对照版...

    compiler-0.9.3-API文档-中英对照版.zip

    包含翻译后的API文档:compiler-0.9.3-javadoc-API文档-中文(简体)-英语-对照版.zip; Maven坐标:com.github.spullara.mustache.java:compiler:0.9.3; 标签:github、java、spullara、compiler、mustache、jar包、...

    scala-compiler-2.11.0-API文档-中文版.zip

    包含翻译后的API文档:scala-compiler-2.11.0-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.scala-lang:scala-compiler:2.11.0; 标签:scala、lang、compiler、jar包、java、中文文档; 使用方法:解压翻译...

    scala-compiler-2.11.12-API文档-中文版.zip

    包含翻译后的API文档:scala-compiler-2.11.12-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.scala-lang:scala-compiler:2.11.12; 标签:scala、lang、compiler、中文文档、jar包、java; 使用方法:解压...

    commons-compiler-3.1.4-API文档-中文版.zip

    包含翻译后的API文档:commons-compiler-3.1.4-javadoc-API文档-中文(简体)版.zip; Maven坐标:org.codehaus.janino:commons-compiler:3.1.4; 标签:codehaus、janino、commons、compiler、中文文档、jar包、java...

    JDK 6.2.3文文档.rar

    Compiler API 轻量级 Http Server API 插入式注解处理API(Pluggable Annotation Processing API) 用Console开发控制台程序 对脚本语言的支持(如:ruby,groovy,javascript) Common Annotations ——————...

Global site tag (gtag.js) - Google Analytics