如何在运行时内存编译Java类,避免文件系统写入操作?
内存中编译Java源码并获取字节码(无磁盘IO)
完全可以通过自定义javax.tools相关类实现纯内存编译,无需任何磁盘IO操作。核心是替换默认的文件管理逻辑,用内存对象替代磁盘文件作为输入输出载体。
核心思路
- 自定义
JavaFileObject实现:- 一个用于提供内存中的源码(输入)
- 另一个用于存储编译后的字节码(输出)
- 自定义
JavaFileManager:覆盖文件获取逻辑,返回上述内存对象,避免磁盘交互 - 使用
JavaCompilerAPI执行编译,最终从内存对象中提取字节码
完整代码示例
import javax.tools.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Set; // 内存源码文件对象:提供String形式的源码 class MemorySourceFileObject extends SimpleJavaFileObject { private final String sourceCode; public MemorySourceFileObject(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; } } // 内存字节码文件对象:存储编译后的字节码 class MemoryByteCodeFileObject extends SimpleJavaFileObject { private final ByteArrayOutputStream byteCodeOutputStream = new ByteArrayOutputStream(); public MemoryByteCodeFileObject(String className) { super(URI.create("byte:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS); } @Override public OutputStream openOutputStream() { return byteCodeOutputStream; } // 获取编译后的字节码数组 public byte[] getByteCode() { return byteCodeOutputStream.toByteArray(); } } // 自定义文件管理器:完全基于内存处理输入输出 class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> { private final Map<String, MemoryByteCodeFileObject> byteCodeFileObjects = new HashMap<>(); protected MemoryJavaFileManager(JavaFileManager fileManager) { super(fileManager); } // 覆盖获取输出文件的逻辑,返回内存字节码对象 @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { MemoryByteCodeFileObject byteCodeFileObject = new MemoryByteCodeFileObject(className); byteCodeFileObjects.put(className, byteCodeFileObject); return byteCodeFileObject; } // 根据类名获取编译后的字节码对象 public MemoryByteCodeFileObject getByteCodeFileObject(String className) { return byteCodeFileObjects.get(className); } } public class InMemoryCompiler { public static byte[] compile(String className, String sourceCode) throws IOException { // 获取系统默认Java编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); if (compiler == null) { throw new IllegalStateException("无法获取Java编译器,请确保运行环境包含JDK(而非JRE)"); } // 创建诊断监听器,用于捕获编译错误 DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>(); // 创建自定义内存文件管理器 try (MemoryJavaFileManager fileManager = new MemoryJavaFileManager(compiler.getStandardFileManager(diagnosticCollector, null, null))) { // 准备内存源码对象 MemorySourceFileObject sourceFileObject = new MemorySourceFileObject(className, sourceCode); // 创建编译任务 JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, diagnosticCollector, null, // 编译选项,比如"-target" "11"等,按需添加 null, Set.of(sourceFileObject) ); // 执行编译 boolean success = task.call(); if (!success) { // 打印编译错误 for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) { System.err.printf("编译错误:%s%n行号:%d%n信息:%s%n", diagnostic.getSource().toUri(), diagnostic.getLineNumber(), diagnostic.getMessage(null)); } throw new RuntimeException("编译失败"); } // 返回编译后的字节码 return fileManager.getByteCodeFileObject(className).getByteCode(); } } // 测试示例 public static void main(String[] args) throws IOException { String className = "com.example.HelloWorld"; String sourceCode = """ package com.example; public class HelloWorld { public void sayHello() { System.out.println("Hello from in-memory compiled class!"); } } """; byte[] byteCode = compile(className, sourceCode); System.out.println("编译成功,字节码长度:" + byteCode.length + " bytes"); } }
关键说明
- 依赖环境:必须使用JDK运行(而非JRE),因为JRE不包含JavaCompiler组件
- 编译选项:可以在
compiler.getTask的第三个参数添加编译选项,比如List.of("-target", "17", "-source", "17")来指定Java版本 - 错误处理:代码中通过
DiagnosticCollector捕获编译错误,方便调试语法或语义问题 - 字节码使用:编译得到的
byte[]可以直接用于后续操作(比如手动加载到类加载器、存储到数据库等),完全不需要写入磁盘
内容的提问来源于stack exchange,提问作者Volodya Lombrozo
相关产品推荐
相关产品推荐

