javaCompilerAPI(java编译api)

编译java文件
 使用Java API来编译Java源代码有非常多方法,目前让我们来看一种最简单的方法,通过JavaCompiler进行编译。
使用ToolProvider.getSystemJavaCompiler来得到一个JavaCompiler接口的实例。
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
 JavaCompiler中最核心的方法是run()。通过这个方法能编译java源代码。
int run(InputStream in, OutputStream out, OutputStream err, String... arguments)
 参数分别用来为:
java编译器提供参数
 得到Java编译器的输出信息
 接收编译器的错误信息,
 一个或多个Java源程式文件
 如果run编译成功,返回  0。
如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.in、System.out和System.err。如果我们要编译一个test.java文件,并将使用标准输入输出,run的使用方法如下:
int results = tool.run(null, null, null, "F:\demo\Test.java");
 完整的例子:
//CompileMain.java
 import javax.tools.JavaCompiler;
 import javax.tools.ToolProvider;
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
  
 public class CompileMain {
  
     public static void main(String[] args) throws IOException {
         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
         int result = compiler.run(null, null, null, "F:\demo\Test.java");
         System.out.println(result == 0 ? "编译成功" : "编译失败");
  
 //执行java 命令 , 空参数, 所在文件夹
         Process process = Runtime.getRuntime().exec("java Test",null,new File("F:\demo\"));
                
  
         BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
         String str;
         while ((str = bufferedReader.readLine()) != null) {
             System.out.println(str);
         }
     }
 }
 public class Test {
  
     public static void main(String[] args) {
         System.out.println("this is a test.java file ,thank you very much");
     }
  
 }
 $ javac CompileMain.java 
  
 $ java CompileMain             
 编译成功
 this is a test.java file ,thank you very much
 编译非文件形式源代码
 JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件。JavaCompiler 类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager 类提供的。
在Java SE6中最佳的方法是使用StandardJavaFileManager类。这个类能非常好地控制输入、输出,并且能通过DiagnosticListener得到诊断信息,而DiagnosticCollector类就是listener的实现。新的 JDK 定义了 javax.tools.FileObject 和 javax.tools.JavaFileObject 接口。任何类,只要实现了这个接口,就可以被 JavaFileManager 识别。

使用StandardJavaFileManager步骤:
建立一个DiagnosticCollector实例
 通过JavaCompiler.getStandardFileManager()方法得到一个StandardFileManager对象。
 使用StandardFileManager获取需要编译的源代码。从文件或者字符流中获取源代码。
 JavaCompiler.getTask()生成编译任务抽象。
 通过CompilationTask.call()方法编译源代码。
 关闭StandardFileManager。
 在使用这种方法调用Java编译时最复杂的方法就是getTask,下面让我们讨论一下getTask方法。这个方法有如下所示的6个参数。
getTask(Writer out,
         JavaFileManager fileManager,
         DiagnosticListener<? super JavaFileObject> diagnosticListener,
         Iterable<String> options,
         Iterable<String> classes,
         Iterable<? extends JavaFileObject> compilationUnits)
 这些参数大多数都可为null。他们的含义所下。
out: 用于输出错误的流,默认是System.err。
 fileManager:标准的文件管理。
 diagnosticListener: 编译器的默认行为。
 options: 编译器的选项
 classes:参和编译的class。
 compilationUnits: 待编译的Java文件,不能为null。
 CompilationTask 提供了 setProcessors(Iterable<? extends Processor>processors)方法,用户可以制定处理 annotation 的处理器。
在使用完getTask前,需要通过StandardJavaFileManager.getJavaFileObjectsFromFiles()或StandardJavaFileManager.getJavaFileObjectsFromStrings方法得到待编译的compilationUnits对象。
也可以通过继承/实现SimpleJavaObject获取带编译的对象。
调用这两个方法的方式如下:
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();
例如:
package win.hgfdodo.dynamic;
  
 import javax.tools.JavaCompiler;
 import javax.tools.JavaFileObject;
 import javax.tools.StandardJavaFileManager;
 import javax.tools.ToolProvider;
 import java.util.Arrays;
  
 public class JavaFileManagerMain {
     public static void main(String[] args) {
         String fullQuanlifiedFileName = "win.hgfdodo.dynamic.".replaceAll("\.", java.io.File.separator) + "Calculator.java";
         JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
         StandardJavaFileManager fileManager =
                 compiler.getStandardFileManager(null, null, null);
  
         Iterable<? extends JavaFileObject> files =
                 fileManager.getJavaFileObjectsFromStrings(
                         Arrays.asList(fullQuanlifiedFileName));
         JavaCompiler.CompilationTask task = compiler.getTask(
                 null, fileManager, null, null, null, files);
  
         Boolean result = task.call();
         if (result == true) {
             System.out.println("Succeeded");
         }
     }
 }
  
 package win.hgfdodo.dynamic;
  
 public class Calculator {
     public int multiply(int multiplicand, int multiplier) {
         return multiplicand * multiplier;
     }
 }
 JavaFileObject获取java源程序
 开发者希望生成 Calculator 的一个测试类,而不是手工编写。使用 compiler API,可以将内存中的一段字符串,编译成一个 CLASS 文件。
定制 JavaFileObject 对象:
package win.hgfdodo.dynamic;
  
 import javax.tools.SimpleJavaFileObject;
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
  
 public class StringObject extends SimpleJavaFileObject {
     private String content = null;
  
     protected StringObject(String className, String contents) throws URISyntaxException {
         super(new URI(className), Kind.SOURCE);
         this.content = contents;
     }
  
     @Override
     public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
         return content;
     }
 }
 SimpleJavaFileObject 是 JavaFileObject 的子类,它提供了默认的实现。继承 SimpleJavaObject 之后,只需要实现 getCharContent 方法。
接下来,在内存中构造 Calculator 的测试类 CalculatorTest,并将代表该类的字符串放置到 StringObject 中,传递给 JavaCompiler.getTask 方法。
具体如下:
package win.hgfdodo.dynamic;
  
 import javax.tools.JavaCompiler;
 import javax.tools.JavaFileObject;
 import javax.tools.StandardJavaFileManager;
 import javax.tools.ToolProvider;
 import java.net.URISyntaxException;
 import java.util.Arrays;
  
 public class StringClassCompilerMain {
     public static void main(String[] args) {
         JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
         StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);
         JavaFileObject testFile = generateTest();
         Iterable<? extends JavaFileObject> classes = Arrays.asList(testFile);
         JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, null, null, null, classes);
         if(task.call()){
             System.out.println("success");
         }else{
             System.out.println("failure!");
         }
  
     }
  
     private static JavaFileObject generateTest() {
         String contents = new String(
                 "package win.hgfdodo.dynamic;" +
                         "class CalculatorTest {
" +
                         "  public void testMultiply() {
" +
                         "    Calculator c = new Calculator();
" +
                         "    System.out.println(c.multiply(2, 4));
" +
                         "  }
" +
                         "  public static void main(String[] args) {
" +
                         "    CalculatorTest ct = new CalculatorTest();
" +
                         "    ct.testMultiply();
" +
                         "  }
" +
                         "}
");
         StringObject so = null;
         try {
             so = new StringObject("win.hgfdodo.dynamic.CalculatorTest", contents);
         } catch (URISyntaxException e) {
             e.printStackTrace();
         }
  
         return so;
  
     }
 }
 采集编译器的诊断信息
 收集编译过程中的诊断信息是JDK6新增的内容。诊断信息,通常指错误、警告或是编译过程中的详尽输出。
JDK 6 通过 Listener 机制,获取这些信息。如果要注册一个 DiagnosticListener,必须使用 CompilationTask 来进行编译,因为 Tool.run 方法没有办法注册 Listener。
步骤:
构造一个 Listener;
 传递给 JavaFileManager 的构造函数;
 编译完成后,获取Diagnostic列表;
 输出诊断信息。
 例子:
package win.hgfdodo.dynamic;
  
 import javax.tools.*;
 import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
  
 public class StringClassCompilerMain {
     public static void main(String[] args) {
         JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
         DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
         StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null);
         JavaFileObject testFile = generateTest();
         Iterable<? extends JavaFileObject> classes = Arrays.asList(testFile);
         JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, collector, null, null, classes);
         if(task.call()){
             System.out.println("success");
         }else{
             System.out.println("failure!");
         }
  
         List<Diagnostic<? extends JavaFileObject>> diagnostics = collector.getDiagnostics();
         for (Diagnostic<? extends JavaFileObject> diagnostic: diagnostics){
             System.out.println("line:"+ diagnostic.getLineNumber());
             System.out.println("msg:"+ diagnostic.getMessage(Locale.ENGLISH));
             System.out.println("source:"+ diagnostic.getSource());
  
         }
     }
  
     private static JavaFileObject generateTest() {
         String contents = new String(
                 "package win.hgfdodo.dynamic;" +
                         "class CalculatorTest {
" +
                         "  public void testMultiply() {
" +
                         "    Calculator c = new Calculator()
" +
                         "    System.out.println(c.multiply(2, 4));
" +
                         "  }
" +
                         "  public static void main(String[] args) {
" +
                         "    CalculatorTest ct = new CalculatorTest();
" +
                         "    ct.testMultiply();
" +
                         "  }
" +
                         "}
");
         StringObject so = null;
         try {
             so = new StringObject("win.hgfdodo.dynamic.CalculatorTest", contents);
         } catch (URISyntaxException e) {
             e.printStackTrace();
         }
  
         return so;
  
     }
 }
 generateTest方法在构造Calculator时,将行尾;去掉,造成java 源文件错误,在编译时,会输出:
line:3
 msg:需要";"
 source:win.hgfdodo.dynamic.StringObject[win.hgfdodo.dynamic.CalculatorTest]
 运行时编译和运行java类
 
CharSequenceJavaFileObject -- 存储源代码
 package win.hgfdodo.compiler;
  
 import javax.tools.SimpleJavaFileObject;
 import java.io.IOException;
 import java.net.URI;
  
 /**
  * 字符串java源代码。JavaFileObject表示
  */
 public class CharSequenceJavaFileObject extends SimpleJavaFileObject {
  
     //表示java源代码
     private CharSequence content;
  
     protected CharSequenceJavaFileObject(String className, String content) {
         super(URI.create("string:///" + className.replaceAll("\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
         this.content = content;
     }
  
     /**
      * 获取需要编译的源代码
      * @param ignoreEncodingErrors
      * @return
      * @throws IOException
      */
     @Override
     public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
         return content;
     }
 }
 JavaClassObject 保存编译结果
 package win.hgfdodo.compiler;
  
 import javax.tools.SimpleJavaFileObject;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.net.URI;
  
 /**
  * 存储编译后的字节码
  */
 public class JavaClassObject extends SimpleJavaFileObject {
  
     /**
      * Compiler编译后的byte数据会存在这个ByteArrayOutputStream对象中,
      * 后面可以取出,加载到JVM中。
      */
     private ByteArrayOutputStream byteArrayOutputStream;
  
     public JavaClassObject(String className, Kind kind) {
         super(URI.create("string:///" + className.replaceAll("\.", "/") + kind.extension), kind);
         this.byteArrayOutputStream = new ByteArrayOutputStream();
     }
  
     /**
      * 覆盖父类SimpleJavaFileObject的方法。
      * 该方法提供给编译器结果输出的OutputStream。
      * 
      * 编译器完成编译后,会将编译结果输出到该 OutputStream 中,我们随后需要使用它获取编译结果
      *
      * @return
      * @throws IOException
      */
     @Override
     public OutputStream openOutputStream() throws IOException {
         return this.byteArrayOutputStream;
     }
  
     /**
      * FileManager会使用该方法获取编译后的byte,然后将类加载到JVM
      */
     public byte[] getBytes() {
         return this.byteArrayOutputStream.toByteArray();
     }
 }
 JavaFileManager 处理编译结果
 JavaFileManager提供了编译结果存储和编译类的加载。
package win.hgfdodo.compiler;
  
 import javax.tools.FileObject;
 import javax.tools.ForwardingJavaFileManager;
 import javax.tools.JavaFileManager;
 import javax.tools.JavaFileObject;
 import java.io.IOException;
 import java.security.SecureClassLoader;
  
 /**
  * 输出字节码到JavaClassFile
  */
 public class ClassFileManager extends ForwardingJavaFileManager {
  
     /**
      * 存储编译后的代码数据
      */
     private JavaClassObject classJavaFileObject;
  
     protected ClassFileManager(JavaFileManager fileManager) {
         super(fileManager);
     }
  
     /**
      * 编译后加载类
      * <p>
      * 返回一个匿名的SecureClassLoader:
      * 加载由JavaCompiler编译后,保存在ClassJavaFileObject中的byte数组。
      */
     @Override
     public ClassLoader getClassLoader(Location location) {
         return new SecureClassLoader() {
             @Override
             protected Class<?> findClass(String name) throws ClassNotFoundException {
                 byte[] bytes = classJavaFileObject.getBytes();
                 return super.defineClass(name, bytes, 0, bytes.length);
             }
         };
     }
  
     /**
      * 给编译器提供JavaClassObject,编译器会将编译结果写进去
      */
     @Override
     public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
         this.classJavaFileObject = new JavaClassObject(className, kind);
         return this.classJavaFileObject;
     }
  
 }
 DynamicCompiler -- 自定义编译器
 DynamicCompiler实现将源代码编译并加载的功能。
package win.hgfdodo.compiler;
  
 import javax.tools.*;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
  
 /**
  * 运行时编译
  */
 public class DynamicCompiler {
     private JavaFileManager fileManager;
  
     public DynamicCompiler() {
         this.fileManager = initManger();
     }
  
     private JavaFileManager initManger() {
         if (fileManager != null) {
             return fileManager;
         } else {
             JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
             DiagnosticCollector diagnosticCollector = new DiagnosticCollector();
             fileManager = new ClassFileManager(javaCompiler.getStandardFileManager(diagnosticCollector, null, null));
             return fileManager;
         }
     }
  
     /**
      * 编译源码并加载,获取Class对象
      * @param fullName
      * @param sourceCode
      * @return
      * @throws ClassNotFoundException
      */
     public Class compileAndLoad(String fullName, String sourceCode) throws ClassNotFoundException {
         JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
         List<JavaFileObject> javaFileObjectList = new ArrayList<JavaFileObject>();
         javaFileObjectList.add(new CharSequenceJavaFileObject(fullName, sourceCode));
         boolean result = javaCompiler.getTask(null, fileManager, null, null, null, javaFileObjectList).call();
         if (result) {
             return this.fileManager.getClassLoader(null).loadClass(fullName);
         } else {
             return Class.forName(fullName);
         }
     }
  
     /**
      * 关闭fileManager
      * @throws IOException
      */
     public void closeFileManager() throws IOException {
         this.fileManager.close();
     }
  
 }
 测试
 package win.hgfdodo.compiler;
  
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
  
 public class DynamicCompilerTest {
     public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
         StringBuilder src = new StringBuilder();
         src.append("package win.hgfdodo.compiler;");
         src.append("public class DynaClass {
");
         src.append("    public String toString() {
");
         src.append("        return "Hello, I am " + ");
         src.append("this.getClass().getSimpleName();
");
         src.append("    }
");
         src.append("}
");
  
         String fullName = "win.hgfdodo.compiler.DynaClass";
  
         DynamicCompiler compiler = new DynamicCompiler();
         Class clz = compiler.compileAndLoad(fullName, src.toString());
  
         System.out.println(clz.getConstructor().newInstance());
         compiler.close();
     }
 }
 编译加载win.hgfdodo.compiler.DynaClass后,创建新的对象,并调用toString()输出:
Hello, I am DynaClass
 参考
 javafile compiler, classloader and run
  
本文根据博客https://my.oschina.net/hgfdoing/blog/3052263
修改而来,注意, 编译.java源码, 和执行.class 都需要制定文件夹,这里我使用了绝对路径
2019年5月23日09:59:23
 ————————————————
 版权声明:本文为CSDN博主「长河」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
 原文链接:https://blog.csdn.net/u010398771/java/article/details/90474813
以上是 javaCompilerAPI(java编译api) 的全部内容, 来源链接: utcz.com/z/516143.html








