`
opensdp
  • 浏览: 77126 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
文章分类
社区版块
存档分类
最新评论

调用Java编译器API编译Java

 
阅读更多

本文由 外刊IT评论   组织翻译


Core Java Technologies Tech Tips

Compiling with the Java Compiler API

调用Java编译器API编译Java

从第一天开始,标准Java平台就缺少能够被调用,去产生Java字节码的编译器接口. 使用Sun实现的平台,一个用户可以通过非标准的 com.sun.tools.javac   包中的Main class 去编译你的代码 (你可以在lib子目录下的 tools.jar 文件里找到它). 然而这个包并没有提供一个标准的公开的编程接口. 使用其它实现的用户必然不能访问这个类. 使用Java SE 6和在JSR-199中定义的它的新的Java编译器接口,你可以从你自己的应用程序里访问javac编译工具了.

有两种方式使用这种工具. 一种是简单的,一种是稍微复杂点但拥有更多选项的. 你首先将会用较简单的一种去编译 "Hello, World"程序,就是下面的这个:

public class Hello {
  public static void main(String args[]) {
    System.out.println("Hello, World");
  }
}
 

要想从Java程序里调用Java编译器,你需要访问JavaCompiler 接口. 除此外,通过访问这个接口,你可以设置源代码的路径,classpath,和目标目录. 通过指定可编译的文件为 JavaFileObject instance ,你可以将它们全部编译. 然而,你并不需要对 JavaFileObject 了解多少.

可以使用 ToolProvider 类去请求 theJavaCompiler 接口的缺省实现. 这个 ToolProvider 类提供了一个 getSystemJavaCompiler() 方法, 它返回一个 JavaCompiler 接口的实例.

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 

使用 JavaCompiler 运行编译最简单的方法是调用在这个接口工具里定义的 run() 方法,它的实现是:

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

分别为前三个缺省参数 System.in , System.out , and System.err 传入 null 值. 参数集 String 对象表示着传入编译器的文件的名称.

这样,你应该像下面这样去编译前面显示的 Hello 源程序:

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

假设没有编译错误,这样会在目标目录里产生一个 Hello.class文件. 如果这里有错误, run() 方法会把它输出到标准错误输出流里,也就是 run()  方法的的第三个参数. 当错误发生时这个方法返回一个非0的结果.

你可以使用下面的代码去编译 Hello.java 源文件:

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

public class CompileIt {
  public static void main(String args[]) throws IOException {
    JavaCompiler compiler =
        ToolProvider.getSystemJavaCompiler();
    int results = compiler.run(
        null, null, null, "Hello.java");
    System.out.println("Result code: " + results);
  }
}
 

一旦你编译了 CompileIt 一次 ,你就可以多次运行它,当你修改了 Hello.java 源程序时或者要重新编译它,你不需要重新编译 CompileIt . 如果没有错误,运行 CompileIt 会产生下面的输出:

> java CompileIt
Result code: 0
 

运行 CompileIt 同样也会在相同的目录下产生一个 Hello.class 文件:

> ls
CompileIt.class
CompileIt.java
Hello.class
Hello.java
 

你可以完事了,因为这样使用标准编译器已经足够了,可是这还有更有用的. 当你需要更好的处理这些结果时,你可以使用第二种方法来访问编译器. 更特别的是,这第二种方式允许开发者将编译输出结果用一种更有意义的方式表现出来,而不是简单的那种送往stdeer的错误文本. 利用 StandardJavaFileManager 类我们有这种更好的途径使用编译器. 这个文件管理器提供了一种方式,用来处理普通文件的输入输出操作. 它同时利用 DiagnosticListener 实例来报告调试信息. 你需要使用的 DiagnosticCollector 类其实是监听器的一种实现.

在搞清楚你需要编译什么之前,你需要一个文件管理器. 生成一个管理器基本上需要两步: 创建一个 DiagnosticCollector 和 使用 JavaCompilergetStandardFileManager() 方法获得一个文件管理器. 把 DiagnosticListener 对象传入 getStandardFileManager() 方法中. 这个监听器可以报告一些非致命的问题,到后来你可以选择性的通过把它传入 getTask() 方法来和编译器共享.

DiagnosticCollector<JavaFileObject> diagnostics =
    new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager =
    compiler.getStandardFileManager(diagnostics, aLocale, aCharset);
 

你也可以往这个调用里传入一个 null 值的诊断器,但这样也就等于用以前的编译器方法了.

在详细查看 StandardJavaFileManager 之前 ,编译过程涉及到 JavaCompiler 的一个方法叫做 getTask() . 它有六个参数,返回一个叫做 CompilationTask 内部类的实例:

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

缺省情况下,大部分它的参数可以是 null.
* out: System.err
* fileManager: compiler's standard file manager
* diagnosticListener: compiler's default behavior
* options: no command-line options to compiler
* classes: no class names for annotation processing

最后一个参数 compilationUnits 却是不能够为null ,因为它是你要去编译的东西. 它把我们又带回了 StandardJavaFileManager 类.注意这个参数类型: Iterable<? extends JavaFileObject> .   StandardJavaFileManager 有两个方法返回这样的结果. 你可以使用一个文件对象的List或者 String 对象的List,用它们来表示文件名:

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

并不仅仅 List ,实际上,任何一个能够标识需要编译的内容的集合的 Iterable 都可以.  List 出现在这里只是因为它容易生成:

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

 

现在你有了编译源文件的所有的必要的信息. 从 getTask( ) 返回的 JavaCompiler.CompilationTask  实现了 Callable .接口 这样,想让任务开始就去调用call()方法.

JavaCompiler.CompilationTask task =
    compiler.getTask(null, fileManager, null, null, null, compilationUnits);
Boolean success = task.call();

 

 

如果没有编译警告和错误,这个call() 方法会编译所有的 compilationUnits 变量指定的文件,以及有依赖关系的可编译的文件. 想要知道是否所有的都成功了,去查看一下返回的 Boolean 值. 只有当所有的编译单元都执行成功了,这个 call() 方法才返回 Boolean.TRUE  . 一旦有任何错误,这个方法就会返回 Boolean.FALSE .

在展示运行这个例子之前,让我们添加最后一个东西,DiagnosticListener , 或者更确切的说,  DiagnosticCollector .的实现类.把这个监听器当作getTask()的第三个参数传递进去,你就可以在编译之后进行一些调式信息的查询了.

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

在最后,你应该调用管理器的close() 方法.

把所有的放在一起,就得到的了下面的程序,让我们重新编译Hello类.

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

public class BigCompile {
  public static void main(String args[]) throws IOException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics =
        new DiagnosticCollector<JavaFileObject>();
    StandardJavaFileManager fileManager =
        compiler.getStandardFileManager(diagnostics, null, null);
    Iterable<? extends JavaFileObject> compilationUnits =
        fileManager.getJavaFileObjectsFromStrings(Arrays.asList("Hello.java"));
    JavaCompiler.CompilationTask task = compiler.getTask(
        null, fileManager, diagnostics, null, null, compilationUnits);
    Boolean success = task.call();
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
      System.console().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));
    }
    fileManager.close();
    System.out.println("Success: " + success);
  }
}
 

 

编译和运行这个程序会输出成功的信息:

> javac BigCompile.java
> java BigCompile
Success: true
 

然而,如果你把 println  方法改成书写错误的 pritnln 方法,当你运行时你会得到下面的信息:

> java BigCompile
Code: compiler.err.cant.resolve.location
Kind: ERROR
Position: 80
Start Position: 70
End Position: 88
Source: Hello.java
Message:  Hello.java:3: cannot find symbol
symbol  : method pritnln(java.lang.String)
location: class java.io.PrintStream
Success: false
 

使用Compiler API,你可以实现比在这篇简要的提示介绍的更多的事情. 例如,你可以控制输入输出的目录或者在集成编译器里高亮一些编译错误. 现在,向 Java Compiler API表示感谢,你可以使用标准API了. For more information on the Java Compiler API and JSR 199, see the JSR 199 specification.

分享到:
评论
5 楼 weiqingfei 2007-04-10  
用途?意义?
4 楼 opensdp 2007-04-08  
simohayha 写道
把文章重新编辑一下吧?




不明白什么意思?重新编辑什么??
3 楼 icefire 2007-04-08  
可以把class当JSP用,呵呵!
继续关注!
2 楼 tsingn 2007-04-08  
这方面的实际应用还没怎么看到,关注中……
1 楼 simohayha 2007-04-08  
把文章重新编辑一下吧?

相关推荐

    JDK1.8(32位和64位)正式版+JDK1.8API帮助文档

    没有JDK的话,无法编译Java程序,如果想只运行Java程序,要确保已安装相应的JRE。(JDK已包含)。 jdk1.8新特性详解:(http://www.oschina.net/translate/everything-about-java-8) JDK包含的基本组件包括: javac...

    SM2密码算法 JAVA 调用演示程序。.zip

    它的设计目标是“一次编写,到处运行(Write Once, Run Anywhere)”,这意味着开发者可以使用Java编写应用程序,并在支持Java的任何平台上无需重新编译即可运行,这得益于其独特的跨平台性,通过Java虚拟机(JVM)...

    JWebAssembly:Java字节码到WebAssembly编译器

    它可以编译任何可编译为Java字节码的语言,例如Clojure,Groovy,JRuby,Jython,Kotlin和Scala。 作为输出,它生成二进制格式(.wasm文件)或文本格式(.wat文件)。 目标是通过WebAssembly在浏览器中本机运行Java...

    ajax调用java实例源码-jayq:jQuery的ClojureScript包装器

    ajax调用java实例源码杰克 ClojureScript 的 jQuery 包装器。 等一下.. 我以为我们不应该使用jQuery? 当 ClojureScript 首次出现时,普遍的看法是,由于 jQuery 与 Google Closure Compiler 不兼容,我们不应该使用...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    1.5.2 Java编译器(Java Compiler) 17 1.5.3 Java类库(Java Class Libraries) 17 1.5.4 Java虚拟机(Java Virtual Machine) 17 1.5.5 HelloWorld的整个流程 17 1.6 小结:我们学会了编译和运行一个Java程序...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    1.5.2 Java编译器(Java Compiler) 17 1.5.3 Java类库(Java Class Libraries) 17 1.5.4 Java虚拟机(Java Virtual Machine) 17 1.5.5 HelloWorld的整个流程 17 1.6 小结:我们学会了编译和运行一个Java程序...

    Java核心技术II(第8版)

    12.6 调用Java方法 12.6.1 实例方法 12.6.2 静态方法 12.6.3 构造器 12.6.4 替代方法调用 12.7 访问数组元素 12.8 错误处理 12.9 使用调用API 12.10 完整的示例:访问Windows注册表 12.10.1 Windows注册表概述 12.10...

    java-servlet-api.doc

    它可以调用Java所提供的大量的API的功能模块。 这份文档说明了JavaServletAPI的类和接口的方法。有关更多的信息,请参看下面的API说明。 Servlet的生命周期 一个Javaservlet具有一个生命周期,这个生命周期定义了一...

    疯狂JAVA讲义

    学生提问:当我们使用编译C程序时,不仅需要指定存放目标文件的位置,也需要指定目标文件的文件名,这里使用javac编译Java程序时怎么不需要指定目标文件的文件名呢? 13 1.5.3 运行Java程序 14 1.5.4 根据...

    JAVA面试题最全集

    给定一个C语言函数,要求实现在java类中进行调用。 45.如何获得数组的长度? 46.访问修饰符“public/private/protected/缺省的修饰符”的使用 47.用关键字final修饰一个类或者方法时,有何意义? 48.掌握类和...

    Java开发工具,自动生成api文档.zip

    包括编译器、构建工具(如Make、Gradle、Maven)等,用于将源代码转换为可执行文件或库,并进行资源打包、优化等处理。 调试与测试: 集成调试器允许开发者逐行执行代码,设置断点、查看变量值、跟踪调用堆栈等...

    java 面试题 总结

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 6、说出Servlet的生命周期,并说出Servlet和CGI的区别。 Servlet被服务器实例化后,容器运行其init方法,...

    Java 虚拟机面试题全面解析(干货)

    Java方法调用 什么是方法调用? Java的方法调用,有什么特殊之处? Java虛拟机调用字节码指令有哪些? 虚拟机是如何执行方法里面的字节码指令的? 解释执行 基于栈的指令集和基于寄存器的指令集 什么是基于栈的指令集? ...

    Qi-API 接口开放平台开发者调用工具.zip

    包括编译器、构建工具(如Make、Gradle、Maven)等,用于将源代码转换为可执行文件或库,并进行资源打包、优化等处理。 调试与测试: 集成调试器允许开发者逐行执行代码,设置断点、查看变量值、跟踪调用堆栈等...

    唯品会Java开发手册.zip

    结合唯品会的内部经验,参考《阿里巴巴Java开发手册》《Clean Code》、《Effective Java》等重磅资料进行了大幅定制,包含核心基础类库VJKit ,问题排查工具VJMap 和 VJTop 三部分。 开发工具在软件开发生命周期中...

    Java经典入门教程pdf完整版

    同时还提供了对BJB( Enterprise java beans)、 Java Servlets aPi、JSP( Java Server pages) 以及ⅫML技术的全面攴持。其最终目的就是成为一个能够使企业开发者大幅缩短投放市场时 间的体系结构。 JEE体系结构提供屮...

    超级有影响力霸气的Java面试题大全文档

    java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 9、说出Servlet的生命周期,并说出Servlet和CGI的区别。  Servlet被服务器实例化后,容器运行其init方法...

    Java开发基础工具集,陆续收录常用的java代码,令代码更加简洁美观.zip

    包括编译器、构建工具(如Make、Gradle、Maven)等,用于将源代码转换为可执行文件或库,并进行资源打包、优化等处理。 调试与测试: 集成调试器允许开发者逐行执行代码,设置断点、查看变量值、跟踪调用堆栈等...

    android,java必备知识,面试知识,工作学习记录.zip

    也包含java数据结构,算法,爬虫,泛型,反射等实现 开发工具在软件开发生命周期中扮演着至关重要的角色,它们旨在简化和加速从概念设计到产品部署的各个环节。以下是开发工具的主要作用: 代码编写与编辑: ...

    滴滴云Java开发者工具套件.zip

    包括编译器、构建工具(如Make、Gradle、Maven)等,用于将源代码转换为可执行文件或库,并进行资源打包、优化等处理。 调试与测试: 集成调试器允许开发者逐行执行代码,设置断点、查看变量值、跟踪调用堆栈等...

Global site tag (gtag.js) - Google Analytics