`
seara
  • 浏览: 624573 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

Java SE6调用Java编译器的两种新方法

阅读更多
本文为原创,如需转载,请注明作者和出处,谢谢!

本文曾发表于天极网: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.inSystem.outSystem.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 /> -->importjava.io.*;
importjavax.tools.*;

publicclasstest_compilerapi
{
publicstaticvoidmain(Stringargs[])throwsIOException
{
JavaCompilercompiler
=ToolProvider.getSystemJavaCompiler();
intresults=compiler.run(null,null,null,"test.java");
System.out.println((results
==0)?"编译成功":"编译失败");
//在程序中运行test
Runtimerun=Runtime.getRuntime();
Processp
=run.exec("javatest");
BufferedInputStreamin
=newBufferedInputStream(p.getInputStream());
BufferedReaderbr
=newBufferedReader(newInputStreamReader(in));
Strings;
while((s=br.readLine())!=null)
System.out.println(s);
}
}


publicclasstest
{
publicstaticvoidmain(String[]args)throwsException
{
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实例以及通过JavaCompilergetStandardFileManager()方法得到一个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
<?superJavaFileObject>diagnosticListener,

Iterable
<String>options,

Iterable
<String>classes,

Iterable
<?extendsJavaFileObject>compilationUnits)

这些参数大多数都可为null。它们的含义所下。

1. out::用于输出错误的流,默认是System.err

2. fileManager::标准的文件管理。

3. diagnosticListener: 编译器的默认行为。

4. options: 编译器的选项

5. classes:参与编译的class

最后一个参数compilationUnits不能为null,因为这个对象保存了你想编译的Java文件。

在使用完getTask后,需要通过StandardJavaFileManagergetJavaFileObjectsFromFilesgetJavaFileObjectsFromStrings方法得到compilationUnits对象。调用这两个方法的方式如下:.


<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->Iterable<?extendsJavaFileObject>getJavaFileObjectsFromFiles(
Iterable
<?extendsFile>files)
Iterable
<?extendsJavaFileObject>getJavaFileObjectsFromStrings(
Iterable
<String>names)

String[]filenames
=…;
Iterable
<?extendsJavaFileObject>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 /> -->importjava.io.*;
importjava.util.*;
importjavax.tools.*;

publicclasstest_compilerapi
{
privatestaticvoidcompilejava()throwsException
{
JavaCompilercompiler
=ToolProvider.getSystemJavaCompiler();
//建立DiagnosticCollector对象
DiagnosticCollector<JavaFileObject>diagnostics
=newDiagnosticCollector<JavaFileObject>();
StandardJavaFileManagerfileManager
=compiler.getStandardFileManager(
diagnostics,
null,null);
//建立用于保存被编译文件名的对象
//每个文件被保存在一个从JavaFileObject继承的类中
Iterable<?extendsJavaFileObject>compilationUnits=fileManager
.getJavaFileObjectsFromStrings(ArraysasList(
"test3.java"));
JavaCompiler.CompilationTasktask
=compiler.getTask(null,fileManager,
diagnostics,
null,null,compilationUnits);
//编译源程序
booleansuccess=task.call();
fileManager.close();
System.out.println((success)
?”编译成功”:”编译失败”);
}
publicstaticvoidmain(Stringargs[])throwsException
{
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 /> -->publicclasstest
{
publicstaticvoidmain(String[]args)throwsException
{
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 /> -->importjava.lang.reflect.*;
importjava.io.*;
importjavax.tools.*;
importjavax.tools.JavaCompiler.CompilationTask;
importjava.util.*;
importjava.net.*;

publicclasstest_compilerapi
{
privatestaticvoidcompilerJava()throwsException
{
JavaCompilercompiler
=ToolProvider.getSystemJavaCompiler();
DiagnosticCollector
<JavaFileObject>diagnostics
=newDiagnosticCollector<JavaFileObject>();
//定义一个StringWriter类,用于写Java程序
StringWriterwriter=newStringWriter();
PrintWriterout
=newPrintWriter(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=newJavaSourceFromString("HelloWorld",writer
.toString());
Iterable
<?extendsJavaFileObject>compilationUnits=Arrays
.asList(file);
JavaCompiler.CompilationTasktask
=compiler.getTask(null,null,
diagnostics,
null,null,compilationUnits);
booleansuccess=task.call();
System.out.println(
"Success:"+success);
//如果成功,通过reflection执行这段Java程序
if(success)
{
System.out.println(
"-----输出-----");
Class.forName(
"HelloWorld").getDeclaredMethod("main",newClass[]
{String[].
class}).invoke(null,newObject[]
{
null});
System.out.println(
"-----输出-----");
}
}
publicstaticvoidmain(Stringargs[])throwsException
{
compilerJava();
}
}
//用于传递源程序的JavaSourceFromString类
classJavaSourceFromStringextendsSimpleJavaFileObject
{
finalStringcode;
JavaSourceFromString(Stringname,Stringcode)
{
super(URI.create("string:///"+name.replace('.','/')
+Kind.SOURCE.extension),Kind.SOURCE);
this.code=code;
}
@Override
publicCharSequencegetCharContent(booleanignoreEncodingErrors)
{
returncode;
}
}




国内最棒的Google Android技术社区(eoeandroid),欢迎访问!

《银河系列原创教程》发布

《Java Web开发速学宝典》出版,欢迎定购

分享到:
评论

相关推荐

    java调用本地编译器编译文件

    获得代码,保存到本地,调用本地编译器编译文件,执行之后返回结果并删除文件。可以更改为接口,集成到web项目里,达到类似菜鸟工具java在线编程的效果,考虑并发的话需要使用多线程

    Java方法反射调用demo

    Java反射 调用空参方法 调用Object类型参数的方法 调用基本类型参数的方法 调用基本类型数组参数的方法 调用String数组参数的方法 调用Object数组参数的方法 调用私有方法

    C++调用Java方法

    Android Studio项目,此Demo实现Java调用C++函数,然后C++函数回调Java方法、纯C++直接调用Java方法,此为github地址链接

    Java虚拟机规范.Java SE 8版.zip

    同时,书中不仅完整地讲述了由Java SE 8所引入的新特性,例如对包含默认实现代码的接口方法所做的调用,还讲述了为支持类型注解及方法参数注解而对class文件格式所做的扩展,并阐明了class文件中各属性的含义,以及...

    55.java方法调用.zip

    55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java方法调用.zip55.java...

    java远程方法调用

    java远程方法调用,可以实现远程调用,仅作参考

    matlab2017 调用vs2017编译器补丁文件

    matlab2017调用vs2017时找不到vs2017编译器,可以把该附件attachment_1487958_17a_win64_2017-05-10\bin\win64\mexopts内的两个文件拷贝到matlab2017的安装目录\bin\win64\mexopts内即可。

    PHP调用java类的两种方法

    Java语言功能强大,因此在许多情况下在php中来调用...在php中调用 Java语言有两种方法,一种是使用php中的Java扩展模块,另一种是使用minij2ee应用服务器提供的SJOP协议实现。这里我们来比较一下这两种方法各自的特点。

    Java虚拟机规范 Java SE 8版-带目录-pdf

    同时,书中不仅完整地讲述了由Java SE 8所引入的新特性,例如对包含默认实现代码的接口方法所做的调用,还讲述了为支持类型注解及方法参数注解而对class文件格式所做的扩展,并阐明了class文件中各属性的含义,以及...

    groovy和Java相互调用1

    Groovy 调用 Java 类groovy 调用 Java class 十分方便,只需要在类前导入该 Java 类,在 Groovy 代码中就可以无缝使用该

    java动态调用方法

    利用java反射原理实现方法的动态调用。

    Java调用存储过程的2种方法

    Java调用存储过程的2种方法 Java调用存储过程的2种方法 Java调用存储过程的2种方法

    JAVA方法调用万年历

    ACCP5.0 JAVA方法调用万年历!采用方法调用的形式来做的万年历·····

    java方法调用

    java方法调用

    java中两种方式调用其他.exe可执行程序

    java中两种方式调用其他.exe可执行程序

    Java调用ILOG规则集的两种实现

    两种方式实现java调用ILOG规则集。

    三种方式实现java远程调用(rmi),绝对可用

    三种方式实现java远程调用(rmi) 方式一:原始方式 方式二:spring 方式三:jndi 解压,放到myeclipse上可用

    57.java带参数方法调用.zip

    57.java带参数方法调用.zip57.java带参数方法调用.zip57.java带参数方法调用.zip57.java带参数方法调用.zip57.java带参数方法调用.zip57.java带参数方法调用.zip57.java带参数方法调用.zip57.java带参数方法调用.zip...

    JAVA调用DLL方法 JAVA调用DLL方

    JAVA调用DLL JAVA调用DLLJAVA调用DLLJAVA调用DLLJAVA调用DLLJAVA调用DLL

    C#调用java类、jar包方法

    C#调用java类、jar包方法C#调用java类、jar包方法C#调用java类、jar包方法C#调用java类、jar包方法C#调用java类、jar包方法。

Global site tag (gtag.js) - Google Analytics