能否通过Java 11特性直接在代码中执行未编译的.java文件?
在Java 11中直接加载并运行未编译的.java文件(无外部依赖)
核心方案:内存编译+自定义类加载器
你期望的极简API确实不存在,但可以基于Java 11内置的javax.toolsAPI(属于java.compiler模块)封装一个工具类,实现不生成物理class文件直接加载.java源码的功能。核心思路是:
- 使用
JavaCompiler进行内存编译,将字节码输出到内存而非磁盘 - 通过自定义类加载器加载内存中的字节码,避免文件IO操作
以下是可直接使用的封装代码:
import javax.tools.*; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; public class InMemoryJavaCompiler { // 加载指定.java文件并返回Class对象 public static Class<?> compileAndLoad(String javaFilePath) throws Exception { File sourceFile = new File(javaFilePath); String className = extractClassName(sourceFile); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // 自定义文件管理器,将编译后的字节码写入内存 MemoryJavaFileManager fileManager = new MemoryJavaFileManager(compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)); // 准备编译任务 List<JavaFileObject> sourceFiles = new ArrayList<>(); sourceFiles.add(new SimpleJavaFileObject(URI.create("file://" + sourceFile.getAbsolutePath()), JavaFileObject.Kind.SOURCE) { @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { return Files.readString(sourceFile.toPath()); } }); // 执行编译 boolean success = compiler.getTask(null, fileManager, null, null, null, sourceFiles).call(); if (!success) { throw new RuntimeException("编译失败:" + javaFilePath); } // 从内存加载类 byte[] classBytes = fileManager.getClassBytes(className); return new ByteArrayClassLoader().defineClass(className, classBytes); } // 从.java文件中提取类名(简单实现,仅处理顶级类) private static String extractClassName(File sourceFile) throws IOException { String content = Files.readString(sourceFile.toPath()); int classIdx = content.indexOf("class "); if (classIdx == -1) { throw new RuntimeException("未找到顶级类定义:" + sourceFile.getName()); } int start = classIdx + 6; int end = content.indexOf(' ', start); if (end == -1) { end = content.indexOf('{', start); } return content.substring(start, end).trim(); } // 自定义文件管理器,用于存储编译后的字节码到内存 static class MemoryJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> { private final ByteArrayJavaClassObject classObject; protected MemoryJavaFileManager(JavaFileManager fileManager) { super(fileManager); this.classObject = new ByteArrayJavaClassObject(); } @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { return classObject; } public byte[] getClassBytes(String className) { return classObject.getBytes(); } } // 存储编译后的字节码的JavaFileObject实现 static class ByteArrayJavaClassObject extends SimpleJavaFileObject { private final ByteArrayOutputStream outputStream; protected ByteArrayJavaClassObject() { super(URI.create("byte:///" + "Dummy.class"), JavaFileObject.Kind.CLASS); this.outputStream = new ByteArrayOutputStream(); } @Override public ByteArrayOutputStream openOutputStream() throws IOException { return outputStream; } public byte[] getBytes() { return outputStream.toByteArray(); } } // 自定义类加载器,加载内存中的字节码 static class ByteArrayClassLoader extends ClassLoader { public Class<?> defineClass(String name, byte[] bytes) { return defineClass(name, bytes, 0, bytes.length); } } }
使用方式非常接近你期望的API:
// 加载并获取Class对象 Class<?> myClass = InMemoryJavaCompiler.compileAndLoad("MyClass.java"); // 创建实例并调用方法 Object instance = myClass.getDeclaredConstructor().newInstance(); myClass.getMethod("myMethod").invoke(instance);
关于javax.script.ScriptEngine的说明
Java标准库确实没有内置支持Java作为脚本语言的ScriptEngine实现。如果需要适配ScriptEngine规范,可以基于上面的内存编译逻辑封装一个自定义ScriptEngine:
- 实现
ScriptEngine和Compilable接口 - 将脚本内容(Java源码)通过内存编译后加载,再反射调用方法
不过这种场景下,直接使用上面的工具类会更轻量,无需适配脚本引擎的额外规范。
关键注意事项
- 确保你的项目依赖
java.compiler模块(Java 11中默认包含,但模块化项目需要显式声明requires java.compiler;) - 上述代码仅处理顶级类,若需要支持内部类、包声明等复杂场景,需要完善类名提取逻辑
- 编译时可以添加额外参数(如
-classpath),通过compiler.getTask()的第三个参数传递编译选项
内容的提问来源于stack exchange,提问作者Bill K
相关产品推荐
相关产品推荐

