java Compiler API

在早期的版本中(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, "test.java");

完整的例子:

1//CompileMain.java 2import javax.tools.JavaCompiler; 3import javax.tools.ToolProvider; 4import java.io.BufferedReader; 5import java.io.IOException; 6import java.io.InputStreamReader; 7 8public class CompileMain { 9 10 public static void main(String[] args) throws IOException { 11 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 12 int result = compiler.run(null, null, null, "win.hgfdodo.dynamic.test.java"); 13 System.out.println(result == 0 ? "编译成功" : "编译失败"); 14 15 Process process = Runtime.getRuntime().exec("java win.hgfdodo.dynamic.test"); 16 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); 17 String str; 18 while ((str = bufferedReader.readLine()) != null) { 19 System.out.println(str); 20 } 21 } 22} 23 24 25//test.java 26public class test { 27 public static void main(String[] args) { 28 System.out.println("This is win.hgfdodo.dynamic.test class!"); 29 } 30} 31 32 33$ javac CompileMain.java 34 35$ java CompileMain 36编译成功 37This is test class!

编译非文件形式源代码

JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件JavaCompiler 类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager 类提供的。

在Java SE6中最佳的方法是使用StandardJavaFileManager类。这个类能非常好地控制输入、输出,并且能通过DiagnosticListener得到诊断信息,而DiagnosticCollector类就是listener的实现。新的 JDK 定义了 javax.tools.FileObjectjavax.tools.JavaFileObject 接口。任何类,只要实现了这个接口,就可以被 JavaFileManager 识别。

编译流程

使用StandardJavaFileManager步骤:

  1. 建立一个DiagnosticCollector实例
  2. 通过JavaCompiler.getStandardFileManager()方法得到一个StandardFileManager对象。
  3. 使用StandardFileManager获取需要编译的源代码。从文件或者字符流中获取源代码。
  4. JavaCompiler.getTask()生成编译任务抽象。
  5. 通过CompilationTask.call()方法编译源代码。
  6. 关闭StandardFileManager

在使用这种方法调用Java编译时最复杂的方法就是getTask,下面让我们讨论一下getTask方法。这个方法有如下所示的6个参数。

1getTask(Writer out, 2 JavaFileManager fileManager, 3 DiagnosticListener<? super JavaFileObject> diagnosticListener, 4 Iterable<String> options, 5 Iterable<String> classes, 6 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获取带编译的对象。

调用这两个方法的方式如下:

1Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) 2Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) 3 4String[] filenames =; 5Iterable<? extends JavaFileObject> compilationUnits = 6fileManager.getJavaFileObjectsFromFiles(Arrays.asList(filenames)); 7 8JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, 9diagnostics, options, null, compilationUnits);

最后需要关闭fileManager.close();

例如:

1package win.hgfdodo.dynamic; 2 3import javax.tools.JavaCompiler; 4import javax.tools.JavaFileObject; 5import javax.tools.StandardJavaFileManager; 6import javax.tools.ToolProvider; 7import java.util.Arrays; 8 9public class JavaFileManagerMain { 10 public static void main(String[] args) { 11 String fullQuanlifiedFileName = "win.hgfdodo.dynamic.".replaceAll("\\.", java.io.File.separator) + "Calculator.java"; 12 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 13 StandardJavaFileManager fileManager = 14 compiler.getStandardFileManager(null, null, null); 15 16 Iterable<? extends JavaFileObject> files = 17 fileManager.getJavaFileObjectsFromStrings( 18 Arrays.asList(fullQuanlifiedFileName)); 19 JavaCompiler.CompilationTask task = compiler.getTask( 20 null, fileManager, null, null, null, files); 21 22 Boolean result = task.call(); 23 if (result == true) { 24 System.out.println("Succeeded"); 25 } 26 } 27} 28 29 30 31package win.hgfdodo.dynamic; 32 33public class Calculator { 34 public int multiply(int multiplicand, int multiplier) { 35 return multiplicand * multiplier; 36 } 37}

JavaFileObject获取java源程序

开发者希望生成 Calculator 的一个测试类,而不是手工编写。使用 compiler API,可以将内存中的一段字符串,编译成一个 CLASS 文件。

定制 JavaFileObject 对象:

1package win.hgfdodo.dynamic; 2 3import javax.tools.SimpleJavaFileObject; 4import java.io.IOException; 5import java.net.URI; 6import java.net.URISyntaxException; 7 8public class StringObject extends SimpleJavaFileObject { 9 private String content = null; 10 11 protected StringObject(String className, String contents) throws URISyntaxException { 12 super(new URI(className), Kind.SOURCE); 13 this.content = contents; 14 } 15 16 @Override 17 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 18 return content; 19 } 20}

SimpleJavaFileObjectJavaFileObject 的子类,它提供了默认的实现。继承 SimpleJavaObject 之后,只需要实现 getCharContent 方法。

接下来,在内存中构造 Calculator 的测试类 CalculatorTest,并将代表该类的字符串放置到 StringObject 中,传递给 JavaCompiler.getTask 方法。

具体如下:

1package win.hgfdodo.dynamic; 2 3import javax.tools.JavaCompiler; 4import javax.tools.JavaFileObject; 5import javax.tools.StandardJavaFileManager; 6import javax.tools.ToolProvider; 7import java.net.URISyntaxException; 8import java.util.Arrays; 9 10public class StringClassCompilerMain { 11 public static void main(String[] args) { 12 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); 13 StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null); 14 JavaFileObject testFile = generateTest(); 15 Iterable<? extends JavaFileObject> classes = Arrays.asList(testFile); 16 JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, null, null, null, classes); 17 if(task.call()){ 18 System.out.println("success"); 19 }else{ 20 System.out.println("failure!"); 21 } 22 23 } 24 25 private static JavaFileObject generateTest() { 26 String contents = new String( 27 "package win.hgfdodo.dynamic;" + 28 "class CalculatorTest {\n" + 29 " public void testMultiply() {\n" + 30 " Calculator c = new Calculator();\n" + 31 " System.out.println(c.multiply(2, 4));\n" + 32 " }\n" + 33 " public static void main(String[] args) {\n" + 34 " CalculatorTest ct = new CalculatorTest();\n" + 35 " ct.testMultiply();\n" + 36 " }\n" + 37 "}\n"); 38 StringObject so = null; 39 try { 40 so = new StringObject("win.hgfdodo.dynamic.CalculatorTest", contents); 41 } catch (URISyntaxException e) { 42 e.printStackTrace(); 43 } 44 45 return so; 46 47 } 48}

采集编译器的诊断信息

收集编译过程中的诊断信息是JDK6新增的内容。诊断信息,通常指错误、警告或是编译过程中的详尽输出。

JDK 6 通过 Listener 机制,获取这些信息。如果要注册一个 DiagnosticListener,必须使用 CompilationTask 来进行编译,因为 Tool.run 方法没有办法注册 Listener

步骤:

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

例子:

1package win.hgfdodo.dynamic; 2 3import javax.tools.*; 4import java.net.URISyntaxException; 5import java.util.Arrays; 6import java.util.List; 7import java.util.Locale; 8 9public class StringClassCompilerMain { 10 public static void main(String[] args) { 11 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); 12 DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>(); 13 StandardJavaFileManager standardJavaFileManager = javaCompiler.getStandardFileManager(null, null, null); 14 JavaFileObject testFile = generateTest(); 15 Iterable<? extends JavaFileObject> classes = Arrays.asList(testFile); 16 JavaCompiler.CompilationTask task = javaCompiler.getTask(null, standardJavaFileManager, collector, null, null, classes); 17 if(task.call()){ 18 System.out.println("success"); 19 }else{ 20 System.out.println("failure!"); 21 } 22 23 List<Diagnostic<? extends JavaFileObject>> diagnostics = collector.getDiagnostics(); 24 for (Diagnostic<? extends JavaFileObject> diagnostic: diagnostics){ 25 System.out.println("line:"+ diagnostic.getLineNumber()); 26 System.out.println("msg:"+ diagnostic.getMessage(Locale.ENGLISH)); 27 System.out.println("source:"+ diagnostic.getSource()); 28 29 } 30 } 31 32 private static JavaFileObject generateTest() { 33 String contents = new String( 34 "package win.hgfdodo.dynamic;" + 35 "class CalculatorTest {\n" + 36 " public void testMultiply() {\n" + 37 " Calculator c = new Calculator()\n" + 38 " System.out.println(c.multiply(2, 4));\n" + 39 " }\n" + 40 " public static void main(String[] args) {\n" + 41 " CalculatorTest ct = new CalculatorTest();\n" + 42 " ct.testMultiply();\n" + 43 " }\n" + 44 "}\n"); 45 StringObject so = null; 46 try { 47 so = new StringObject("win.hgfdodo.dynamic.CalculatorTest", contents); 48 } catch (URISyntaxException e) { 49 e.printStackTrace(); 50 } 51 52 return so; 53 54 } 55}

generateTest方法在构造Calculator时,将行尾;去掉,造成java 源文件错误,在编译时,会输出:

1line:3 2msg:需要';' 3source:win.hgfdodo.dynamic.StringObject[win.hgfdodo.dynamic.CalculatorTest]

运行时编译和运行java类

运行时编译运行图解

CharSequenceJavaFileObject -- 存储源代码

1package win.hgfdodo.compiler; 2 3import javax.tools.SimpleJavaFileObject; 4import java.io.IOException; 5import java.net.URI; 6 7/** 8 * 字符串java源代码。JavaFileObject表示 9 */ 10public class CharSequenceJavaFileObject extends SimpleJavaFileObject { 11 12 //表示java源代码 13 private CharSequence content; 14 15 protected CharSequenceJavaFileObject(String className, String content) { 16 super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE); 17 this.content = content; 18 } 19 20 /** 21 * 获取需要编译的源代码 22 * @param ignoreEncodingErrors 23 * @return 24 * @throws IOException 25 */ 26 @Override 27 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { 28 return content; 29 } 30}

JavaClassObject 保存编译结果

1package win.hgfdodo.compiler; 2 3import javax.tools.SimpleJavaFileObject; 4import java.io.ByteArrayOutputStream; 5import java.io.IOException; 6import java.io.OutputStream; 7import java.net.URI; 8 9/** 10 * 存储编译后的字节码 11 */ 12public class JavaClassObject extends SimpleJavaFileObject { 13 14 /** 15 * Compiler编译后的byte数据会存在这个ByteArrayOutputStream对象中, 16 * 后面可以取出,加载到JVM中。 17 */ 18 private ByteArrayOutputStream byteArrayOutputStream; 19 20 public JavaClassObject(String className, Kind kind) { 21 super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind); 22 this.byteArrayOutputStream = new ByteArrayOutputStream(); 23 } 24 25 /** 26 * 覆盖父类SimpleJavaFileObject的方法。 27 * 该方法提供给编译器结果输出的OutputStream。 28 * 29 * 编译器完成编译后,会将编译结果输出到该 OutputStream 中,我们随后需要使用它获取编译结果 30 * 31 * @return 32 * @throws IOException 33 */ 34 @Override 35 public OutputStream openOutputStream() throws IOException { 36 return this.byteArrayOutputStream; 37 } 38 39 /** 40 * FileManager会使用该方法获取编译后的byte,然后将类加载到JVM 41 */ 42 public byte[] getBytes() { 43 return this.byteArrayOutputStream.toByteArray(); 44 } 45}

JavaFileManager 处理编译结果

JavaFileManager提供了编译结果存储编译类的加载

1package win.hgfdodo.compiler; 2 3import javax.tools.FileObject; 4import javax.tools.ForwardingJavaFileManager; 5import javax.tools.JavaFileManager; 6import javax.tools.JavaFileObject; 7import java.io.IOException; 8import java.security.SecureClassLoader; 9 10/** 11 * 输出字节码到JavaClassFile 12 */ 13public class ClassFileManager extends ForwardingJavaFileManager { 14 15 /** 16 * 存储编译后的代码数据 17 */ 18 private JavaClassObject classJavaFileObject; 19 20 protected ClassFileManager(JavaFileManager fileManager) { 21 super(fileManager); 22 } 23 24 /** 25 * 编译后加载类 26 * <p> 27 * 返回一个匿名的SecureClassLoader: 28 * 加载由JavaCompiler编译后,保存在ClassJavaFileObject中的byte数组。 29 */ 30 @Override 31 public ClassLoader getClassLoader(Location location) { 32 return new SecureClassLoader() { 33 @Override 34 protected Class<?> findClass(String name) throws ClassNotFoundException { 35 byte[] bytes = classJavaFileObject.getBytes(); 36 return super.defineClass(name, bytes, 0, bytes.length); 37 } 38 }; 39 } 40 41 /** 42 * 给编译器提供JavaClassObject,编译器会将编译结果写进去 43 */ 44 @Override 45 public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { 46 this.classJavaFileObject = new JavaClassObject(className, kind); 47 return this.classJavaFileObject; 48 } 49 50}

DynamicCompiler -- 自定义编译器

DynamicCompiler实现将源代码编译并加载的功能。

1package win.hgfdodo.compiler; 2 3import javax.tools.*; 4import java.io.IOException; 5import java.util.ArrayList; 6import java.util.List; 7 8/** 9 * 运行时编译 10 */ 11public class DynamicCompiler { 12 private JavaFileManager fileManager; 13 14 public DynamicCompiler() { 15 this.fileManager = initManger(); 16 } 17 18 private JavaFileManager initManger() { 19 if (fileManager != null) { 20 return fileManager; 21 } else { 22 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); 23 DiagnosticCollector diagnosticCollector = new DiagnosticCollector(); 24 fileManager = new ClassFileManager(javaCompiler.getStandardFileManager(diagnosticCollector, null, null)); 25 return fileManager; 26 } 27 } 28 29 /** 30 * 编译源码并加载,获取Class对象 31 * @param fullName 32 * @param sourceCode 33 * @return 34 * @throws ClassNotFoundException 35 */ 36 public Class compileAndLoad(String fullName, String sourceCode) throws ClassNotFoundException { 37 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); 38 List<JavaFileObject> javaFileObjectList = new ArrayList<JavaFileObject>(); 39 javaFileObjectList.add(new CharSequenceJavaFileObject(fullName, sourceCode)); 40 boolean result = javaCompiler.getTask(null, fileManager, null, null, null, javaFileObjectList).call(); 41 if (result) { 42 return this.fileManager.getClassLoader(null).loadClass(fullName); 43 } else { 44 return Class.forName(fullName); 45 } 46 } 47 48 /** 49 * 关闭fileManager 50 * @throws IOException 51 */ 52 public void closeFileManager() throws IOException { 53 this.fileManager.close(); 54 } 55 56}

测试

1package win.hgfdodo.compiler; 2 3import java.io.IOException; 4import java.lang.reflect.InvocationTargetException; 5 6public class DynamicCompilerTest { 7 public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException { 8 StringBuilder src = new StringBuilder(); 9 src.append("package win.hgfdodo.compiler;"); 10 src.append("public class DynaClass {\n"); 11 src.append(" public String toString() {\n"); 12 src.append(" return \"Hello, I am \" + "); 13 src.append("this.getClass().getSimpleName();\n"); 14 src.append(" }\n"); 15 src.append("}\n"); 16 17 String fullName = "win.hgfdodo.compiler.DynaClass"; 18 19 DynamicCompiler compiler = new DynamicCompiler(); 20 Class clz = compiler.compileAndLoad(fullName, src.toString()); 21 22 System.out.println(clz.getConstructor().newInstance()); 23 compiler.close(); 24 } 25}

编译加载win.hgfdodo.compiler.DynaClass后,创建新的对象,并调用toString()输出:

Hello, I am DynaClass

参考

  1. javafile compiler, classloader and run

--Posted from Rpc

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Java日期时间API系列31

  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )