`
lt200819
  • 浏览: 188056 次
  • 性别: 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 支持,我们拭目以待。
分享到:
评论

相关推荐

    Flex3 compiler API (En)

    ### Flex3 Compiler API详解 #### 一、简介 Flex3 Compiler API 是一款强大的工具,它允许开发者通过Java应用程序来编译Flex应用。这种能力为那些希望利用Java的强大功能与Flex的丰富界面相结合的开发者提供了极大...

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

    JavaCompiler --JDK6 API 的简介(java动态编译) JavaCompiler 是 Java 中的一个编译器接口,提供了编译 Java 源代码的功能。在 Java SE6 中,JavaCompiler 接口是 javax.tools 包的一部分,提供了标准的方式来...

    JDK1.6新特性与实战例子

    JDK6引入的Compiler API允许在运行时动态编译Java源代码。以下是一个简单的例子: ```java import javax.tools.JavaCompiler; import javax.tools.ToolProvider; import java.io.File; import java.util.Arrays; ...

    javaapi和源码-Code-Simulator:一个Java程序,该Java程序使用CompilerAPI导入Java源代码,然后使用Ja

    开发者可以查看其源码,理解API的具体应用,学习如何在实际项目中使用Java Compiler API。同时,开源也意味着社区的支持和持续改进,开发者可以通过参与贡献,推动项目的发展。 5. 安全与性能考虑 动态编译虽然...

    通过JavaCompiler进行编译java文件(转载)

    Java Compiler API,即`javax.tools.JavaCompiler`,提供了这样的功能,使得我们可以直接在程序中完成Java源代码的编译过程。本文将深入探讨如何使用JavaCompiler API来编译Java源文件。 首先,`JavaCompiler`是...

    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的新特性...

    Java编译器API学习1

    在Java世界中,Java Compiler API(也称为JSR 199)提供了一种强大的机制,允许我们在运行时编译Java源代码。这个API使得开发者能够动态地创建、修改和编译Java源代码,极大地扩展了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...

    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...

    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.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; 使用...

    plexus-compiler--2.8.4.jar.rar

    这个压缩包文件"plexus-compiler--2.8.4.jar.rar"包含了Plexus Compiler的三个关键模块,分别是plexus-compiler-api-2.8.4.jar、plexus-compiler-javac-2.8.4.jar和plexus-compiler-manager-2.8.4.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、API文档、中英对照版...

Global site tag (gtag.js) - Google Analytics