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)
参数分别用来为:
- java编译器提供参数
- 得到Java编译器的输出信息
- 接收编译器的错误信息,
- 一个或多个Java源程式文件
如果run编译成功,返回\(0\)。
如果前3个参数传入的是null
,那么run方法将以标准的输入、输出代替,即System.in
、System.out
和System.err
。如果我们要编译一个test.java
文件,并将使用标准输入输出,run的使用方法如下:
int results = tool.run(null, null, null, "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, "win.hgfdodo.dynamic.test.java"); System.out.println(result == 0 ? "编译成功" : "编译失败"); Process process = Runtime.getRuntime().exec("java win.hgfdodo.dynamic.test"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); String str; while ((str = bufferedReader.readLine()) != null) { System.out.println(str); } } }
//test.java public class test { public static void main(String[] args) { System.out.println("This is win.hgfdodo.dynamic.test class!"); } }
$ javac CompileMain.java $ java CompileMain 编译成功 This is test class!
编译非文件形式源代码
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 {\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
。
步骤:
- 构造一个
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 {\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 */ @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 {\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
参考
--Posted from Rpc

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
使用Nginx反向代理到go-fastdfs
背景 go-fastdfs是支持http协议的一款分布式文件系统,在一般的项目中,很少是直接将文件系统的地址暴露出来的,大多数都会通过nginx等软件进行反代过去,由于我司的业务和网络环境场景相对特殊,由公网部分(公有云)和内网部分(私有云)组成的混合云网络体系,公有云主要就是作为一个出口和入口以及运行一些审计认证等应用,对上游请求进行处理,从而减少私有云的处理次数,提升性能。那么也正是因为这样,在公网的环境下,要访问到私有云提供的服务则必须使用反向代理。同样道理,对于文件系统的访问也如此,如何在nginx中进行配置才能使得外部的网络请求可以反向代理到go-fastdfs呢?本文将逐步阐述。 一般配置 在一般的情况下,熟悉nginx的朋友都知道,如果需要配置反向代理,直接写一个location上下文和proxy模块即可,如果需要自定义前缀,使用一个rewrite模块即可。简单例子如下: location ~ /dfs/group([0-9]) { proxy_pass http://localhost:8080; rewrite ^/dfs/(.*)$ /$1 break; proxy...
- 下一篇
epoll 的本质是什么?
从事服务端开发,少不了要接触网络编程。epoll 作为 Linux 下高性能网络服务器的必备技术至关重要,nginx、Redis、Skynet 和大部分游戏服务器都使用到这一多路复用技术。 epoll 很重要,但是 epoll 与 select 的区别是什么呢?epoll 高效的原因是什么? 网上虽然也有不少讲解 epoll 的文章,但要么是过于浅显,或者陷入源码解析,很少能有通俗易懂的。笔者于是决定编写此文,让缺乏专业背景知识的读者也能够明白 epoll 的原理。 文章核心思想是:要让读者清晰明白 epoll 为什么性能好。 本文会从网卡接收数据的流程讲起,串联起 CPU 中断、操作系统进程调度等知识;再一步步分析阻塞接收数据、select 到 epoll 的进化过程;最后探究 epoll 的实现细节。 一、从网卡接收数据说起 下边是一个典型的计算机结构图,计算机由 CPU、存储器(内存)与网络接口等部件组成,了解 epoll 本质的第一步,要从硬件的角度看计算机怎样接收网络数据。 计算机结构图(图片来源:Linux内核完全注释之微型计算机组成结构) 下图展示了网卡接收数据的过程。...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- MySQL8.0.19开启GTID主从同步CentOS8
- Mario游戏-低调大师作品
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题