java中的CompileAPI入门及使用
介绍
java5之前我们可以通过java提供的tools.jar来操作java编译器,java6提供了新的API,让我们可以更方便的调用。包名为javax.tools。
使用
通过文件编译
String filePath = "D:\\Client.java";
//获取java编译器
JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
//编译
int result = javaCompiler.run(null, null, null, filePath);
System.out.println(result);
结果为0表示编译成功,在相同目录下生成了Client.class文件。
编译参数依次为
- java编译器提供参数,如果为null,以System.in代替
- 得到Java编译器的输出信息,如果为null,以System.out代替
- 接收编译器的错误信息,如果为null,以System.err代替
- 一个或多个Java源程式文件
通过非文件格式编译
java还提供了编译其他形式的源文件的功能,如内存字符串文本,数据库读取的文本。
public class JavaFileManagerMain {
public static void main(String[] args) {
//文件路径
String fullQuanlifiedFileName = "D:\\Client.java";
//获取编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
//获取文件管理器 参数依次为错误监听器,区域对象,编码
StandardJavaFileManager fileManager =
compiler.getStandardFileManager(null, null, null);
//通过文件全路径获取要编译的文件对象
Iterable<? extends JavaFileObject> files =
fileManager.getJavaFileObjectsFromStrings(
Arrays.asList(fullQuanlifiedFileName));
//创建编译任务 参数为错误输出流,文件管理器,错误处理器,编译器选项,参与编译的class,带编译的java文件
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, null, null, null, files);
//执行任务
Boolean result = task.call();
if (result) {
System.out.println("Succeeded");
}
}
}
接下来实现从内存中读取待编译对象
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) {
return content;
}
}
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 = "package com.imooc.sourcecode.java.javacompile.test3;" +
"class Test {\n" +
" public static void main(String[] args) {\n" +
" System.out.println(\"success\");\n" +
" }\n" +
"}\n";
StringObject so = null;
try {
so = new StringObject("com.imooc.sourcecode.java.javacompile.test3.Test", contents);
} catch (URISyntaxException e) {
e.printStackTrace();
}
return so;
}
}
结果编译成功。
实现在运行期编译及加载类
定义源代码存储类
/**
* 待编译对象 存储待编译的字符串
*/
public class JavaSourceFileObject extends SimpleJavaFileObject {
//表示java源代码
private CharSequence content;
protected JavaSourceFileObject(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;
}
}
定义编译结果存储类
/**
* 存储编译之后的class内容
*/
public class JavaTargetFileObject extends SimpleJavaFileObject {
/**
* Compiler编译后的byte数据会存在这个ByteArrayOutputStream对象中,
* 后面可以取出,加载到JVM中。
*/
private ByteArrayOutputStream byteArrayOutputStream;
public JavaTargetFileObject(String className, Kind kind) {
super(URI.create("string:///" + className.replaceAll("\\.", "/") + kind.extension), kind);
this.byteArrayOutputStream = new ByteArrayOutputStream();
}
/**
* 覆盖父类SimpleJavaFileObject的方法。
* 该方法提供给编译器结果输出的OutputStream。
* <p>
* 编译器完成编译后,会将编译结果输出到该 OutputStream 中,我们随后需要使用它获取编译结果
*
* @return
* @throws IOException
*/
@Override
public OutputStream openOutputStream() throws IOException {
return this.byteArrayOutputStream;
}
/**
* FileManager会使用该方法获取编译后的byte,然后将类加载到JVM
*/
public byte[] getBytes() {
return this.byteArrayOutputStream.toByteArray();
}
}
定义自己的文件管理器
/**
* 内存文件管理器
* @see JavaTargetFileObject
*/
public class ClassFileManager extends ForwardingJavaFileManager {
/**
* 存储编译后的代码数据
*/
private JavaTargetFileObject 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 JavaTargetFileObject(className, kind);
return this.classJavaFileObject;
}
}
定义一个实现类编译和加载
/**
* 运行时编译
*/
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<JavaFileObject> 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<>();
javaFileObjectList.add(new JavaSourceFileObject(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 close() throws IOException {
this.fileManager.close();
}
}
参考jdk11中的JShell实现,核心类为TaskFactory和MemoryFileManager。