8 Java 编译 ¶
约 286 个字 92 行代码 预计阅读时间 3 分钟
8.2 动态编译 ¶
JDK 提供了工具包 javax.tools 让开发者实现编译(实际场景中通常由 maven, gradle 等开发工具代为完成
- 获取一个 javax.tools.JavaCompiler 实例。
- 基于 Java 文件对象初始化一个编译任务 javax.tools.JavaCompiler$CompilationTask 实例。
- CompilationTask 实例执行
我们熟知的 javac 编译器其实就是 JavaCompiler 接口的实现,在 JDK11 中,对应的实现类为com.sun.tools.javac.api.JavacTool
。在 JDK8 中不存在 JavaCompiler 接口,具体的编译入口类为com.sun.tools.javac.main.JavaCompiler
。
Example¶
为了将编译后的字节码保存到内存而非输出到文件中,我们需要自定义实现 JavaFileObject,方便起见继承 SimpleJavaFileObject。
class InMemoryJavaFileObject extends SimpleJavaFileObject {
private final String sourceCode;
protected InMemoryJavaFileObject(String className, String sourceCode) {
super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
this.sourceCode = sourceCode;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return sourceCode;
}
}
重写 ClassLoader 覆盖原来的ClassLoader#findClass()
方法,用于搜索自定义的 JavaFileObject 实例,从而提取对应的字节码字节数组进行装载
class CustomClassLoader extends ClassLoader {
private final Map<String, ByteArrayOutputStream> compiledBytes;// 哈希表作为缓存
public CustomClassLoader(Map<String, ByteArrayOutputStream> compiledBytes) {
this.compiledBytes = compiledBytes;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
ByteArrayOutputStream byteStream = compiledBytes.get(name);
if (byteStream == null) {
throw new ClassNotFoundException(name);
}
byte[] bytes = byteStream.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
}
}
JavaFileManager 是 Java 文件的抽象管理器。通过实现自定义的 JavaFileManager,管理字符串类型的源代码。为了简单起见直接继承 ForwardingJavaFileManager
class ClassFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private final Map<String, ByteArrayOutputStream> compiledBytes;
protected ClassFileManager(StandardJavaFileManager fileManager, Map<String, ByteArrayOutputStream> compiledBytes) {
super(fileManager);
this.compiledBytes = compiledBytes;
}
@Override
public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, Kind kind, FileObject sibling) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
compiledBytes.put(className, outputStream);
return new SimpleJavaFileObject(URI.create("mem:///" + className + kind.extension), kind) {
@Override
public OutputStream openOutputStream() {
return outputStream;
}
};
}
}
public class dyncomp {
public static void main(String[] args) throws Exception {
// 1. 源代码
String sourceCode =
"public class HelloWorld{" +
" public static void main(String[] args) throws Exception {" +
" Runtime.getRuntime().exec(\"calc\");" +
" }" +
"}";
// 2. 将源代码保存到内存中
JavaFileObject file = new InMemoryJavaFileObject("HelloWorld", sourceCode);
// 3. 获取系统Java编译器
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// 4. 准备编译输出
StringWriter output = new StringWriter();
// 5. 使用自定义文件管理器
Map<String, ByteArrayOutputStream> compiledBytes = new HashMap<>();
StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
JavaFileManager fileManager = new ClassFileManager(standardFileManager, compiledBytes);
// 6. 编译代码
boolean success = compiler.getTask(output, fileManager, null, null, null, Arrays.asList(file)).call();
// 7. 检查编译结果
if (success) {
System.out.println("Compilation successful!");
// 8. 加载并执行编译后的类
CustomClassLoader classLoader = new CustomClassLoader(compiledBytes);
Class<?> clazz = classLoader.findClass("HelloWorld");
clazz.getMethod("main", String[].class).invoke(null, (Object) new String[]{});
} else {
System.out.println("Compilation failed:");
System.out.println(output.toString());
}
}
}
参考资料 ¶
最后更新:
2024年8月22日 15:25:14
创建日期: 2024年8月22日 15:25:14
创建日期: 2024年8月22日 15:25:14