在早期的版本中(Java SE5及以前版本)中只能通过tools.jar中的com.sun.tools.javac包来调用Java编译器,但由于tools.jar不是标准的Java库,在使用时必须要设置这个jar的路径。而在Java SE6中为我们提供了标准的包来操作Java编译器,这就是javax.tools包。

编译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)

参数分别用来为:

  1. java编译器提供参数
  2. 得到Java编译器的输出信息
  3. 接收编译器的错误信息,
  4. 一个或多个Java源程式文件

如果run编译成功,返回  0。

如果前3个参数传入的是null,那么run方法将以标准的输入、输出代替,即System.inSystem.outSystem.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步骤:

  1. 建立一个DiagnosticCollector实例
  2. 通过JavaCompiler.getStandardFileManager()方法得到一个StandardFileManager对象。
  3. 使用StandardFileManager获取需要编译的源代码。从文件或者字符流中获取源代码。
  4. JavaCompiler.getTask()生成编译任务抽象。
  5. 通过CompilationTask.call()方法编译源代码。
  6. 关闭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;}@Overridepublic 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 {\n" +"  public void testMultiply() {\n" +"    Calculator c = new Calculator();\n" +"    System.out.println(c.multiply(2, 4));\n" +"  }\n" +"  public static void main(String[] args) {\n" +"    CalculatorTest ct = new CalculatorTest();\n" +"    ct.testMultiply();\n" +"  }\n" +"}\n");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

步骤:

  1. 构造一个 Listener
  2. 传递给 JavaFileManager 的构造函数;
  3. 编译完成后,获取Diagnostic列表;
  4. 输出诊断信息。

例子:

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 {\n" +"  public void testMultiply() {\n" +"    Calculator c = new Calculator()\n" +"    System.out.println(c.multiply(2, 4));\n" +"  }\n" +"  public static void main(String[] args) {\n" +"    CalculatorTest ct = new CalculatorTest();\n" +"    ct.testMultiply();\n" +"  }\n" +"}\n");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*/@Overridepublic 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*/@Overridepublic 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数组。*/@Overridepublic ClassLoader getClassLoader(Location location) {return new SecureClassLoader() {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] bytes = classJavaFileObject.getBytes();return super.defineClass(name, bytes, 0, bytes.length);}};}/*** 给编译器提供JavaClassObject,编译器会将编译结果写进去*/@Overridepublic 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 {\n");src.append("    public String toString() {\n");src.append("        return \"Hello, I am \" + ");src.append("this.getClass().getSimpleName();\n");src.append("    }\n");src.append("}\n");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

参考

  1. javafile compiler, classloader and run

本文根据博客https://my.oschina.net/hgfdoing/blog/3052263

修改而来,注意, 编译.java源码, 和执行.class 都需要制定文件夹,这里我使用了绝对路径

2019年5月23日09:59:23

java Compiler API (java编译api)相关推荐

  1. java基础总结-java技术栈快速复习

    java基础 java基础概念 java概述和语言背景 java语言是没有sun公司(Stanford University Network:斯坦福大学网络)在1995年推出的计算机语言 java之父 ...

  2. java编译器API——使用编译工具

    [0]README 0.1)以下内容转自: http://suntips.iteye.com/blog/69002 0.2)for basic java compiler API, please vi ...

  3. java jdk1.6的新特性 DeskTop SystemTray Console类 Compiler API Http Server API

    DeskTop DeskTop类允许一个Java应用程序启动本地的另一个应用程序去处理URI或文件请求 Desktop desktop=Desktop.getDesktop(); //判断是否支持桌面 ...

  4. java平台脚本+java编译器API

    [0]README 0.1)本文文字描述转自 core java volume 2, 旨在学习  java平台脚本+java编译器API 的 基础知识: ----------------------- ...

  5. java开发checklist,Java API设计CheckList

    API设计原则:正确.好名.易用.易学.够快.够小.但我们从来不缺原则,〜〜〜 Interface 1.The Importance of Being Use Case Oriented,一个接口应当 ...

  6. 【java】简述CGLIB常用API

    1.概述 转载:简述CGLIB常用API 类似:[Spring]CGLIB动态代理 CGLIB,即Code Generation Library,是一个强大的.高性能的代码生成库.其被广泛应用于AOP ...

  7. JAVA入门————API(常用API)

    下面给各位扩展一些JAVA开发中常用的API 文档注释(ApiDoc): import javax.crypto.spec.PSource; 文档注释是功能级注释,只在三个地方使用,分别是:类,方法, ...

  8. Java包括jvm及API,Java基础(下)(JVM、API)

    Java基础(下) 第三部分:Java源程序的编辑 我们知道,计算机是不能直接理解源代码中的高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言编写的程序. 翻译 ...

  9. java SE(六)——API类

    API类 常用API 什么是API? 一.String类 String简单介绍 String类概述 String类创建对象的2种方式 String类常见面试题 String类常用API--字符串内容比 ...

最新文章

  1. 使用Tesseract-OCR训练文字识别记录
  2. Gridview分页模板
  3. Annotation注解(有源代码)
  4. 爬取股票信息(股票代码+价格)
  5. P5431-[模板]乘法逆元2【递推】
  6. Arrays类中的fill(用于填充数组)
  7. linux expect sftp,expect实现sftp文件同步
  8. linux和Windows之间互传文件
  9. python删除某个文件夹_Python 实现删除某路径下文件及文件夹
  10. 成吉思汗:意志征服世界——读书笔记
  11. [原创]如何写好SqlHelper 之终章
  12. python一维数组排序_【Python】数组排序
  13. 超市登录系统 java_超市订单管理系统,登录功能实现
  14. 金蝶计算机快捷键,金蝶KIS专业版常用快捷键列表.doc
  15. word文档更新目录为什么更新不了?
  16. rax Picture组件
  17. 统计学习之第三天(可汗学院公开课:统计学)
  18. 计算机 桌面显示桌面图标不见了,显示桌面图标不见了怎么办,小编教你电脑显示桌面图标不见了怎么解决...
  19. 基于JavaWeb的文明城市宣传系统
  20. Linux fsck命令详解

热门文章

  1. python中的换行符是什么_python的换行符是什么?_后端开发
  2. Fundamentals of Software Architecture:An Engineering Approach学习笔记
  3. 氪8号积木机器人编程_氪1号 能力风暴 教育机器人 积木系列 (AI,智能,编程)
  4. 在64位的操作系统下运行masm
  5. 开发小白入职第一天流程
  6. 计算机电磁信息泄密及其防护
  7. 华为云数据库稳定可靠-即开即用
  8. 论文学习报告_201106
  9. 历届试题 对局匹配-动态规划
  10. 红米Note11TPro和荣耀70Pro哪个值得买 两者配置对比