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.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测试成功! " );
    }
}

编译成功的输出结果:
编译成功
JavaCompiler 测试成功
编译失败的输出结果:
test.java:9:  找不到符号
符号: 方法 printlnln(java.lang.String)
位置:  java.io.PrintStream
              System.out.printlnln("JavaCompiler 测试成功!");
                        ^
错误
编译失败
二、使用 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)
这些参数大多数都可为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);

     最后需要关闭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();
    }
}

     如果想得到具体的编译错误,可以对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 ));

被编译的test.java 代码如下:
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 文件。

Iterable < String >  options  =  Arrays.asList( " -d " " d:\\ " );
JavaCompiler.CompilationTask task 
=  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 文件。

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;
    }
}





 本文转自 androidguy 51CTO博客,原文链接:http://blog.51cto.com/androidguy/215370,如需转载请自行联系原作者

相关文章
|
6天前
|
算法 Java Linux
java制作海报二:java使用Graphics2D 在图片上合成另一个照片,并将照片切割成头像,头像切割成圆形方法详解
这篇文章介绍了如何使用Java的Graphics2D类在图片上合成另一个照片,并将照片切割成圆形头像的方法。
16 1
java制作海报二:java使用Graphics2D 在图片上合成另一个照片,并将照片切割成头像,头像切割成圆形方法详解
|
4天前
|
Java 编译器
Java“返回类型为 void 的方法不能返回一个值”解决
在 Java 中,如果一个方法的返回类型被声明为 void,那么该方法不应该包含返回值的语句。如果尝试从这样的方法中返回一个值,编译器将报错。解决办法是移除返回值语句或更改方法的返回类型。
|
4天前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
13 2
|
4天前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
11 1
|
4天前
|
Java
让星星⭐月亮告诉你,Java synchronized(*.class) synchronized 方法 synchronized(this)分析
本文通过Java代码示例,介绍了`synchronized`关键字在类和实例方法上的使用。总结了三种情况:1) 类级别的锁,多个实例对象在同一时刻只能有一个获取锁;2) 实例方法级别的锁,多个实例对象可以同时执行;3) 同一实例对象的多个线程,同一时刻只能有一个线程执行同步方法。
6 1
|
9天前
|
Java 编译器
在Java中,关于final、static关键字与方法的重写和继承【易错点】
在Java中,关于final、static关键字与方法的重写和继承【易错点】
18 5
|
28天前
|
Java
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
本文介绍了Java中抽象类和抽象方法的使用,以及ArrayList的基本操作,包括添加、获取、删除元素和判断列表是否为空。
20 2
java基础(12)抽象类以及抽象方法abstract以及ArrayList对象使用
|
6天前
|
存储 算法 Java
java制作海报六:Graphics2D的RenderingHints方法参数详解,包括解决文字不清晰,抗锯齿问题
这篇文章是关于如何在Java中使用Graphics2D的RenderingHints方法来提高海报制作的图像质量和文字清晰度,包括抗锯齿和解决文字不清晰问题的技术详解。
13 0
java制作海报六:Graphics2D的RenderingHints方法参数详解,包括解决文字不清晰,抗锯齿问题
|
9天前
|
Java 编译器 C语言
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
【一步一步了解Java系列】:Java中的方法对标C语言中的函数
15 3
|
9天前
|
Java 开发者
Java“类 Y 中的方法 X 不能应用于给定类型”解决
在Java中遇到“类Y中的方法X无法应用于给定类型”的错误时,通常是因为方法调用时的参数类型与定义不符。解决此问题需检查方法签名,确保传递的参数类型正确无误,或使用显式类型转换以匹配方法所需的参数类型。这种错误提示帮助开发者及时修正类型不匹配的问题。