“ 本文将探查 javax.tools 包中,并演示如何使用它们实现Java代码的在线编译 。javax.tools 包以一种通用的方式对这些概念进行了抽象化,使您能够从备用的源代码对象提供源代码,而不要求源代码必须位于文件系统中”
在一些网站上有过刷题经历的人,一定会用过在线运行代码的功能,这一篇文章我们就是针对这一功能,来看看如何在线运行我们编辑的Java代码?实际上在JDK1.6的版本中,提供了这样一个包Javax.tools,它可以实现Java 源代码编译,使您能够添加动态功能来扩展静态应用程序,该包是Java 编程语言编译器框架的主要部分,此框架允许框架的客户端查找并运行程序中的编译器。该框架还为结构化访问诊断(DiagnosticListener)提供服务提供者接口(SPI),为重写文件访问提供文件抽象(JavaFileManager 和 JavaFileObject)
官方文档:https://docs.oracle.com/javase/8/docs/api/index.html
在文章前面的部分我们先了解该包下的一些文件,后面的部分我们具体实现在线运行Java代码。
针对JDK1.6
接口
Diagnostic<S> 用于从工具进行诊断的接口。
DiagnosticListener<S> 用于接收来自工具的诊断的接口
FileObject 工具的文件抽象。
JavaCompiler 从程序中调用 Java™ 编程语言编译器的接口。
JavaCompiler.CompilationTask 表示编译任务的 future 的接口
JavaFileManager 在 Java™ 编程语言源和类文件之上进行操作的工具的文件管理器。
JavaFileManager.Location 文件对象位置的接口
JavaFileObject 在 Java™ 编程语言源和类文件上进行操作的工具的文件抽象。
OptionChecker 识别选项的接口。
StandardJavaFileManager 基于 java.io.File 的文件管理器。
Tool 可以从程序中调用的工具的公共接口
类
DiagnosticCollector<S> 提供将诊断收集到一个列表中的简单方法
ForwardingFileObject<F extends FileObject> 将调用转发到给定的文件对象。
ForwardingJavaFileManager<M extends JavaFileManager> 将调用转发到给定的文件管理器
ForwardingJavaFileObject<F extends JavaFileObject> 将调用转发到给定的文件对象。
SimpleJavaFileObject 为JavaFileObject 中的大多数方法提供简单实现。
ToolProvider 为查找工具提供者提供方法,例如,编译器的提供者。
枚举
Diagnostic.Kind 诊断的种类,例如,错误或者警告。
JavaFileObject.Kind JavaFileObject 的种类。
StandardLocation 文件对象的标准位置。
这一节我们主要来看,上面那些接口具体有什么用,类就不说了,可以去看上面的描述
Diagnostic 通俗的说就是对代码的报错进行诊断,它通常报告源文件中特定位置的问题,但是并非所有诊断都与位置或文件关联。
DiagnosticListener改接口定义了一个方法report,在编译的代码被发现问题时执行,其定义的方法被DiagnosticCollector实现
FileObject 在此上下文中,表示常规文件和其他数据源的抽象。例如,文件对象可用于表示数据库中的常规文件、内存缓存或数据。 如果发生安全异常,此接口中的所有方法都可能抛出SecurityException
JavaFileObject 继承FileObject接口,其内部定义了枚举类Kind,表明JavaFileObject 的种类。
JavaCompiler 为编译器的接口,个人觉得其是包的核心,我们会通过下面的方式,获取编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaCompiler.CompilationTask 表示编译任务的未来的接口。编译任务尚未启动。要启动该任务,请调用call方法。 在调用调用方法之前,可以配置任务的其他方面,例如,通过调用setProcessors方法。
JavaFileManager内容管理器,在JavaCompiler的getTask方法中,我们需要传入该参数。
JavaFileManager.Location为JavaFileManager内部接口, 用于确定在何处放置或搜索文件对象。
StandardJavaFileManager继承JavaFileManager,是基于java.io.File的文件管理器,获取该类实例的常用方法是使用JavaCompiler的getStandardFileManager:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics =new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics, null, null);
Tool可从程序中调用的工具的公共接口。工具通常是命令行程序,如编译器。平台上可用的工具集由供应商定义。其内部仅仅有一个方法isSupportedOption
我们先看一下运行效果,我们可以看到编译跟正常运行没有区别(除了速度)
上面实现没有引入其他自定义开发文件类,如果需要import其他自定义开发文件类,可以选择使用反射来实现:
Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("weaver.conn.RecordSet");
Method[] ms = clazz.getMethods();
Object ob = clazz.newInstance();
System.out.println("===ms" + ms.length);
Method m = clazz.getMethod("execute", new Class[]{String.class});
System.out.println("====0");
Object o = m.invoke(ob, "select * from hrmResource ");
System.out.println("===x" + o.getClass().getSimpleName());
Method m2 = clazz.getMethod("getCounts");
Integer i = (Integer) m2.invoke(ob);
System.out.println("===i" + i);
实现步骤
第一步
定义DynaComplierString类及其构造器
//类全名
private String fullClassName;
//源代码
private String sourceCode;
//存放编译之后的字节码(key:类全名,value:编译之后输出的字节码)
private Map<String, ByteJavaFileObject> javaFileObjectMap = new ConcurrentHashMap<String, ByteJavaFileObject>();
//获取java的编译器
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//存放编译过程中输出的信息
private DiagnosticCollector<JavaFileObject> diagnosticsCollector = new DiagnosticCollector<JavaFileObject>();
//执行结果(控制台输出的内容)
private String runResult;
//编译耗时(单位ms)
private long compilerTakeTime;
//运行耗时(单位ms)
private long runTakeTime;
public DynaComplierString(String sourceCode) {
this.sourceCode = sourceCode;
this.fullClassName = getFullClassName(sourceCode);
}
其中fullClassName是获取代码的类名
public static String getFullClassName(String sourceCode) {
String className = "";
Pattern pattern = Pattern.compile("package\\s+\\S+\\s*;");
Matcher matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className = matcher.group().replaceFirst("package", "").replace(";", "").trim() + ".";
}
pattern = Pattern.compile("class\\s+\\S+\\s+\\{");
matcher = pattern.matcher(sourceCode);
if (matcher.find()) {
className += matcher.group().replaceFirst("class", "").replace("{", "").trim();
}
return className;
}
第二步
自定义一个JavaFileManage来控制编译之后字节码的输出位置
private class StringJavaFileManage extends ForwardingJavaFileManager {
StringJavaFileManage(JavaFileManager fileManager) {
super(fileManager);
}
//获取输出的文件对象,它表示给定位置处指定类型的指定类。
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
ByteJavaFileObject javaFileObject = new ByteJavaFileObject(className, kind);
javaFileObjectMap.put(className, javaFileObject);
return javaFileObject;
}
}
自定义一个字符串的源码对象
private class StringJavaFileObject extends SimpleJavaFileObject {
//等待编译的源码字段
private String contents;
//java源代码 => StringJavaFileObject对象 的时候使用
public StringJavaFileObject(String className, String contents) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + Kind.SOURCE.extension), Kind.SOURCE);
this.contents = contents;
}
//字符串源码会调用该方法
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return contents;
}
}
编译
public boolean compiler() {
long startTime = System.currentTimeMillis();
//标准的内容管理器,更换成自己的实现,覆盖部分方法
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnosticsCollector, null, null);
JavaFileManager javaFileManager = new StringJavaFileManage(standardFileManager);
//构造源代码对象
JavaFileObject javaFileObject = new StringJavaFileObject(fullClassName, sourceCode);
//获取一个编译任务
JavaCompiler.CompilationTask task = compiler.getTask(null, javaFileManager, diagnosticsCollector, null, null, Arrays.asList(javaFileObject));
//设置编译耗时
compilerTakeTime = System.currentTimeMillis() - startTime;
return task.call();
}
第三步
public void runMainMethod() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, UnsupportedEncodingException {
PrintStream out = System.out;
try {
long startTime = System.currentTimeMillis();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PrintStream printStream = new PrintStream(outputStream);
//PrintStream PrintStream = new PrintStream("/Users/andy/Desktop/tem.sql"); //输出到文件
System.setOut(printStream);
StringClassLoader scl = new StringClassLoader();
Class<?> aClass = scl.findClass(fullClassName);
Method main = aClass.getMethod("main", String[].class);
Object[] pars = new Object[]{1};
pars[0] = new String[]{};
main.invoke(null, pars); //调用main方法
//设置运行耗时
runTakeTime = System.currentTimeMillis() - startTime;
//设置打印输出的内容,根据应用服务器来设置GBK还是UTF-8
runResult = new String(outputStream.toByteArray(), "gbk");
} finally {
//还原默认打印的对象
System.setOut(out);
}
}
第四步
public String getRunResult() {
return runResult;
}
第五步
/**
* @return 编译信息(错误 警告)
*/
public String getCompilerMessage() {
StringBuilder sb = new StringBuilder();
List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticsCollector.getDiagnostics();
for (Diagnostic diagnostic : diagnostics) {
sb.append(diagnostic.toString()).append("\r\n");
}
return sb.toString();
}
完整代码请前往小程序进行复制下载