假设我有一个
String
,其中包含 .java
文件的内容。是否有任何 API 允许我将此源文件编译为虚拟 .class
文件(即生成内容并将其存储在内存中,而不是在磁盘上创建实际的物理 .class
文件)?然后这个“虚拟”.class
就会在JVM中加载并执行?
编辑1:我想这样做的唯一原因是因为有时,我的应用程序可能没有写入权限。
Java 确实有一个 compilation API 来动态编译文件,但我不知道有一个选项不会将类文件保存到磁盘。您始终可以使用
ClassLoader
并动态加载这些类,然后使用它们。您可以通过重写 getFileForOutput 方法来加载内存中的类。
可选地,该文件管理器可能会将同级视为 在哪里放置输出。该提示的确切语义是 未指定。 JDK 编译器,例如 javac,将放置类 文件与原始源文件位于同一目录中,除非 提供了class文件输出目录。为了促进这种行为, javac 可能会在以下情况下提供原始源文件作为同级文件 调用这个方法。
另一种选择是使用像 BeanShell 这样的解释器来为您运行 java 代码。它像代码一样执行脚本,并且可以在 repl 模式下工作。
javax.tools
拥有您需要的一切,但需要一些哄骗才能停止存储类文件。幸运的是,它可以通过大约 100 行代码的单个类来完成:
package simple.tools;
import java.io.*;
import java.net.URI;
import java.util.*;
import javax.tools.*;
import javax.tools.JavaCompiler.CompilationTask;
public class SimpleCompiler {
public static JavaFileObject sourceFile(String name, String source) {
return inputFile(name, JavaFileObject.Kind.SOURCE, source);
}
private static URI uri(String name, JavaFileObject.Kind kind) {
return URI.create(name.replace('.', '/') + kind.extension);
}
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
private final JavaFileManager manager =
new ForwardingJavaFileManager<JavaFileManager>(compiler.getStandardFileManager(null, null, null)) {
@Override
public JavaFileObject getJavaFileForOutput(Location l, String name, JavaFileObject.Kind kind, FileObject f) {
return outputFile(name, kind);
}
};
private static JavaFileObject inputFile(String name, JavaFileObject.Kind kind, String content) {
return new SimpleJavaFileObject(uri(name, kind), kind) {
@Override
public CharSequence getCharContent(boolean b) {
return content;
}
};
}
private JavaFileObject outputFile(String name, JavaFileObject.Kind kind) {
return new SimpleJavaFileObject(uri(name, kind), kind) {
@Override
public OutputStream openOutputStream() {
return outputStream(name);
}
};
}
private final Map<String, byte[]> classes = new HashMap<>();
private OutputStream outputStream(String name) {
return new ByteArrayOutputStream() {
@Override
public void close() {
classes.put(name, toByteArray());
}
};
}
private final ClassLoader loader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = classes.get(name);
if (bytes == null) throw new ClassNotFoundException(name);
return super.defineClass(name, bytes, 0, bytes.length);
}
};
public Class<?> compile(String name, String source) {
compile(sourceFile(name, source));
try { return loadClass(name); }
catch (ClassNotFoundException e) { throw new IllegalStateException(e.toString(), e); }
}
public void compile(JavaFileObject... files) {
compile(Arrays.asList(files));
}
public void compile(List<JavaFileObject> files) {
if (files.isEmpty()) throw new RuntimeException("No input files");
DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
CompilationTask task = compiler.getTask(null, manager, collector, null, null, files);
boolean success = task.call();
check(success, collector);
}
private void check(boolean success, DiagnosticCollector<?> collector) {
for (Diagnostic<?> diagnostic : collector.getDiagnostics()) {
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
String message = diagnostic.getMessage(Locale.US);
throw new RuntimeException(message.split("[\r\n]")[0]);
}
}
if (! success) throw new RuntimeException("Unknown error");
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loader.loadClass(name);
}
}