通过自己实现JavaFileObject 并“修补”JavaFileManager,您可以将编译器保留在内存中,然后通过暴露ClassLoader 的defineClass(),您可以加载生成的字节数组并通过反射启动main() .
这是一个精简的变体,它实际上是按照您的要求进行的,从文件中加载源代码(无论如何,这是与 Files.readString() 的一行)。但是,这可能是您可以完全留给已修补的 JavaFileManager 的东西,只是我专注于将源代码保留在内存中,这就是为什么有注释的 String src = "..."; 块。
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import javax.lang.model.element.*;
import javax.tools.*;
public class SOCompileTestDense {
public static void main(String[] args) throws Exception {
String name="Test";
// String src="public class Test {\r\n" +
// " public static void main(String[] args) {\r\n" +
// " System.out.println(\"Hello \"+args[0]+\"!\");\r\n" +
// " }\r\n" +
// "}";
String src=Files.readString(Paths.get(name+".java"));
System.out.println(src);
List<JFO> sources=Arrays.asList(new JFO(name,src));
Map<String,JFO> files=new HashMap<>();
JavaCompiler jc=ToolProvider.getSystemJavaCompiler();
JavaCompiler.CompilationTask ct=jc.getTask(null, new JFM(jc.getStandardFileManager(null, null, null),files), null, null, null, sources);
System.out.println(ct.call());
Class<?> clazz=new CL().defineClass(name, files.get(name).baos.toByteArray());
clazz.getDeclaredMethod("main", String[].class).invoke(null, (Object)new String[] {"StackOverflow"});
}
static class JFO implements JavaFileObject {
final String name;
final String src;
JFO(String name,String src){
this.name=name;
this.src=src;
}
public URI toUri() {
try {
return new URI(name);
} catch(URISyntaxException use) {use.printStackTrace();};
return null;
}
public String getName() {
return name;
}
ByteArrayOutputStream baos;
public OutputStream openOutputStream() throws IOException {
baos=new ByteArrayOutputStream();
return baos;
}
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return src;
}
public Kind getKind() {
return Kind.SOURCE;
}
public boolean isNameCompatible(String simpleName, Kind kind) {
return name.equals(simpleName);
}
public InputStream openInputStream() throws IOException {return null;}
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {return null;}
public Writer openWriter() throws IOException {return null;}
public long getLastModified() {return 0;}
public boolean delete() {return false;}
public NestingKind getNestingKind() {return null;}
public Modifier getAccessLevel() {return null;}
}
static class JFM implements JavaFileManager {
final JavaFileManager jfm;
final Map<String, JFO> files;
JFM(JavaFileManager jfm, Map<String, JFO> files){
this.jfm=jfm;
this.files=files;
}
public int isSupportedOption(String option) {
return jfm.isSupportedOption(option);
}
public ClassLoader getClassLoader(Location location) {
return jfm.getClassLoader(location);
}
public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
return jfm.list(location, packageName, kinds, recurse);
}
public String inferBinaryName(Location location, JavaFileObject file) {
return jfm.inferBinaryName(location, file);
}
public boolean handleOption(String current, Iterator<String> remaining) {
return jfm.handleOption(current, remaining);
}
public boolean hasLocation(Location location) {
return jfm.hasLocation(location);
}
public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
return jfm.getJavaFileForInput(location, className, kind);
}
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
JFO jfo=new JFO(className,null);
files.put(className, jfo);
return jfo;
}
public String inferModuleName(Location location) throws IOException {
return jfm.inferModuleName(location);
}
public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
return jfm.listLocationsForModules(location);
}
public boolean isSameFile(FileObject a, FileObject b) {return false;}
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {return null;}
public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {return null;}
public void flush() throws IOException {}
public void close() throws IOException {}
}
static class CL extends ClassLoader {
public Class<?> defineClass(String name,byte array[]) {
return defineClass(name, array, 0, array.length);
}
}
}
这是一个整洁、简短的版本,它的输出如下所示:
public class Test {
public static void main(String[] args) {
System.out.println("Hello "+args[0]+"!");
}
}
true
Hello StackOverflow!
Ideone 上有一个更长的输出变体,https://ideone.com/DAXIlH,如果你下载那个变体并删除各种System.out.println(); 调用前面的//,它会显示更多的东西,它们是对 Ideone 来说太过分了。
实际上,我所做的是将所有方法实现为throw new Error();(参见 Ideone 上的代码),并在实际调用它们时逐渐用有意义的东西填充它们。实际上它非常简单,我遇到的唯一问题是JavaFileManager 接口的一对default 方法。由于 eclipse 没有提供这些的 stub 实现,当代码在没有触及我的 throw new Error(); 行的情况下死掉时,这有点令人惊讶。