本文曾发表于天极网: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.in
、System.out
和System.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( null , null , null , " 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测试成功! " );
}
}
import javax.tools. * ;
public class test_compilerapi
{
public static void main(String args[]) throws IOException
{
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int results = compiler.run( null , null , null , " 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
实例以及通过JavaCompiler
的getStandardFileManager()
方法得到一个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)
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
后,需要通过StandardJavaFileManager
的getJavaFileObjectsFromFiles
或getJavaFileObjectsFromStrings
方法得到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);
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, null , null );
// 建立用于保存被编译文件名的对象
// 每个文件被保存在一个从JavaFileObject继承的类中
Iterable extends JavaFileObject > compilationUnits = fileManager
.getJavaFileObjectsFromStrings(Arrays asList( " test3.java " ));
JavaCompiler.CompilationTask task = compiler.getTask( null , fileManager,
diagnostics, null , null , compilationUnits);
// 编译源程序
boolean success = task.call();
fileManager.close();
System.out.println((success) ? ”编译成功”:”编译失败”);
}
public static void main(String args[]) throws Exception
{
compilejava();
}
}
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, null , null );
// 建立用于保存被编译文件名的对象
// 每个文件被保存在一个从JavaFileObject继承的类中
Iterable extends JavaFileObject > compilationUnits = fileManager
.getJavaFileObjectsFromStrings(Arrays asList( " test3.java " ));
JavaCompiler.CompilationTask task = compiler.getTask( null , fileManager,
diagnostics, null , null , 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 ));
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测试成功! " );
}
}
{
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);
JavaCompiler.CompilationTask task = compiler.getTask( null , fileManager,
diagnostics, options, null , compilationUnits);
在上面的例子中options
处的参数为null
,而要传递编译器的参数,就需要将options
传入。
有时我们编译一个Java
源程序文件,而这个源程序文件需要另几个Java
文件,而这些Java
文件又在另外一个目录,那么这就需要为编译器指定这些文件所在的目录。
Iterable 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( null , null ,
diagnostics, null , null , 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( null , new 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;
}
}
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( null , null ,
diagnostics, null , null , 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( null , new 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;
}
}