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 测试成功!");
                        ^
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)
这些参数大多数都可为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;
    }
}


你可能感兴趣的:(java,职场,休闲,Java编译器,SE6)