本文为原创,如需转载,请注明作者和出处,谢谢!
本文曾发表于天极网: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的完整代码。
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
import
java.io.
*
;
import
javax.tools.
*
;
public
class
test_compilerapi
{
public
static
void
main(Stringargs[])
throws
IOException
{
JavaCompilercompiler
=
ToolProvider.getSystemJavaCompiler();
int
results
=
compiler.run(
null
,
null
,
null
,
"
test.java
"
);
System.out.println((results
==
0
)
?
"
编译成功
"
:
"
编译失败
"
);
//
在程序中运行test
Runtimerun
=
Runtime.getRuntime();
Processp
=
run.exec(
"
javatest
"
);
BufferedInputStreamin
=
new
BufferedInputStream(p.getInputStream());
BufferedReaderbr
=
new
BufferedReader(
new
InputStreamReader(in));
Strings;
while
((s
=
br.readLine())
!=
null
)
System.out.println(s);
}
}
public
class
test
{
public
static
void
main(String[]args)
throws
Exception
{
System.out.println(
"
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个参数。
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
getTask(Writerout,
JavaFileManagerfileManager,
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对象。调用这两个方法的方式如下:.
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
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.CompilationTasktask
=
compiler.getTask(
null
,fileManager,
diagnostics,options,
null
,compilationUnits);
最后需要关闭fileManager.close();
下面是一个完整的演示程序。
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
import
java.io.
*
;
import
java.util.
*
;
import
javax.tools.
*
;
public
class
test_compilerapi
{
private
static
void
compilejava()
throws
Exception
{
JavaCompilercompiler
=
ToolProvider.getSystemJavaCompiler();
//
建立DiagnosticCollector对象
DiagnosticCollector
<
JavaFileObject
>
diagnostics
=
new
DiagnosticCollector
<
JavaFileObject
>
();
StandardJavaFileManagerfileManager
=
compiler.getStandardFileManager(
diagnostics,
null
,
null
);
//
建立用于保存被编译文件名的对象
//
每个文件被保存在一个从JavaFileObject继承的类中
Iterable
<?
extends
JavaFileObject
>
compilationUnits
=
fileManager
.getJavaFileObjectsFromStrings(ArraysasList(
"
test3.java
"
));
JavaCompiler.CompilationTasktask
=
compiler.getTask(
null
,fileManager,
diagnostics,
null
,
null
,compilationUnits);
//
编译源程序
boolean
success
=
task.call();
fileManager.close();
System.out.println((success)
?
”编译成功”:”编译失败”);
}
public
static
void
main(Stringargs[])
throws
Exception
{
compilejava();
}
}
如果想得到具体的编译错误,可以对Diagnostics进行扫描,代码如下:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
for
(Diagnosticdiagnostic:diagnostics.getDiagnostics())
System.out.printf(
"
Code:%s%n
"
+
"
Kind:%s%n
"
+
"
Position:%s%n
"
+
"
StartPosition:%s%n
"
+
"
EndPosition:%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代码如下:
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
public
class
test
{
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文件。
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
Iterable
<
String
>
options
=
Arrays.asList(
"
-d
"
,
"
d:\\
"
);
JavaCompiler.CompilationTasktask
=
compiler.getTask(
null
,fileManager,
diagnostics,options,
null
,compilationUnits);
在上面的例子中options处的参数为null,而要传递编译器的参数,就需要将options传入。
有时我们编译一个Java源程序文件,而这个源程序文件需要另几个Java文件,而这些Java文件又在另外一个目录,那么这就需要为编译器指定这些文件所在的目录。
Iterable<String> options = Arrays.asList("-sourcepath", "d:""src");
上面的代码指定的被编译Java文件所依赖的源文件所在的目录。
三、在内存中编译
JavaCompiler不仅可以编译硬盘上的Java文件,而且还可以编译内存中的Java代码,然后使用reflection来运行它们。我们可以编写一个JavaSourceFromString类,通过这个类可以输入Java源代码。一但建立这个对象,你可以向其中输入任意的Java代码,然后编译和运行,而且无需向硬盘上写.class文件。
<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->
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
{
JavaCompilercompiler
=
ToolProvider.getSystemJavaCompiler();
DiagnosticCollector
<
JavaFileObject
>
diagnostics
=
new
DiagnosticCollector
<
JavaFileObject
>
();
//
定义一个StringWriter类,用于写Java程序
StringWriterwriter
=
new
StringWriter();
PrintWriterout
=
new
PrintWriter(writer);
//
开始写Java程序
out.println(
"
publicclassHelloWorld{
"
);
out.println(
"
publicstaticvoidmain(Stringargs[]){
"
);
out.println(
"
System.out.println(\
"
Hello,World\
"
);
"
);
out.println(
"
}
"
);
out.println(
"
}
"
);
out.close();
//
为这段代码取个名子:HelloWorld,以便以后使用reflection调用
JavaFileObjectfile
=
new
JavaSourceFromString(
"
HelloWorld
"
,writer
.toString());
Iterable
<?
extends
JavaFileObject
>
compilationUnits
=
Arrays
.asList(file);
JavaCompiler.CompilationTasktask
=
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(Stringargs[])
throws
Exception
{
compilerJava();
}
}
//
用于传递源程序的JavaSourceFromString类
class
JavaSourceFromString
extends
SimpleJavaFileObject
{
final
Stringcode;
JavaSourceFromString(Stringname,Stringcode)
{
super
(URI.create(
"
string:///
"
+
name.replace(
'
.
'
,
'
/
'
)
+
Kind.SOURCE.extension),Kind.SOURCE);
this
.code
=
code;
}
@Override
public
CharSequencegetCharContent(
boolean
ignoreEncodingErrors)
{
return
code;
}
}
国内最棒的Google Android技术社区(eoeandroid),欢迎访问!
《银河系列原创教程》发布
《Java Web开发速学宝典》出版,欢迎定购