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
?类来封装一个编译操作。这个类可以通过:
diagnosticListener,Iterable options,Iterable classes,Iterable extends JavaFileObject> compilationUnits
)
方法得到。关于每个参数的含义,请参见 JDK 文档。传递不同的参数,会得到不同的?CompilationTask
。通过构造这个类,一个编译过程可以被分成多步。进一步,CompilationTask
?提供了?setProcessors(Iterable extends Processor>processors)
?方法,用户可以制定处理 annotation 的处理器。图 1 展示了通过?CompilationTask
?进行编译的过程:

下面的例子通过构造?CompilationTask
?分多步编译一组 Java 源文件。
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);
16 Iterable<? extends JavaFileObject> files =
fileManager.getJavaFileObjectsFromStrings(
Arrays.asList(fullQuanlifiedFileName));
17 JavaCompiler.CompilationTask task = compiler.getTask(
null,fileManager,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 文件。
09 public CharSequence getCharContent(boolean ignoreEncodingErrors)
throws IOException {
10 return contents;
11 }
12 }
SimpleJavaFileObject
?是?JavaFileObject
?的子类,它提供了默认的实现。继承?SimpleJavaObject
?之后,只需要实现?getCharContent
?方法。如??中的 9-11 行所示。接下来,在内存中构造?Calculator
?的测试类?CalculatorTest
,并将代表该类的字符串放置到StringObject
?中,传递给?JavaCompiler
?的?getTask
?方法。?展现了这些步骤。
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);
12 JavaFileObject file = constructTestor();
13 Iterable<? extends JavaFileObject> files = Arrays.asList(file);
14 JavaCompiler.CompilationTask task = compiler.getTask (
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 }
实现逻辑和??相似。不同的是在 20-30 行,程序在内存中构造了?CalculatorTest
?类,并且通过?StringObject
?的构造函数,将内存中的字符串,转换成了?JavaFileObject
?对象。
第三个新增加的功能,是收集编译过程中的诊断信息。诊断信息,通常指错误、警告或是编译过程中的详尽输出。JDK 6 通过?Listener
?机制,获取这些信息。如果要注册一个?DiagnosticListener
,必须使用?CompilationTask
?来进行编译,因为 Tool 的?run
?方法没有办法注册Listener
。步骤很简单,先构造一个?Listener
,然后传递给?JavaFileManager
?的构造函数。?对??进行了改动,展示了如何注册一个?DiagnosticListener
。
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);
16 Iterable<? extends JavaFileObject> files =
fileManager.getJavaFileObjectsFromStrings(
Arrays.asList(fullQuanlifiedFileName));
17 DiagnosticCollector collector =
new DiagnosticCollector();
18 JavaCompiler.CompilationTask task =
compiler.getTask(null,collector,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,得到以下输出:
5
Message->math/Calculator.java:5: ';' expected
Source->compiler.err.expected
实际上,也可以由用户自己定制。?给出了一个定制的?Listener
。
{
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 支持,我们拭目以待。
原文:http://www.ibm.com/developerworks/cn/java/j-lo-jse64/index.html
(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!