我遇到了一个在内存中编译Java生成的类的问题,我希望有人能指出我的问题在哪里。我将简单的例子,并试图尽可能简单地解释。
我有一些正在生成的代码,看起来像这样:
package packOne;
public final class TestClassOne {
public static String doSomething() {
return "one";
}
}
个字符
这些是在运行时生成的,我需要编译它们并使它们可用。为此,我在Compiler
类中使用JavaCompiler
,它(再次简化)看起来像这样:
public class Compiler {
private final JavaCompiler javaCompiler;
private final DynamicClassLoader dynamicClassLoader;
private final DiagnosticCollector<JavaFileObject> diagnostics;
private final ByteCodeGeneratingFileManager fileManager;
public Compiler(DynamicClassLoader dynamicClassLoader) {
this.dynamicClassLoader = dynamicClassLoader;
this.javaCompiler = ToolProvider.getSystemJavaCompiler();
this.diagnostics = new DiagnosticCollector<>();
this.fileManager = new ByteCodeGeneratingFileManager(javaCompiler.getStandardFileManager(diagnostics, null, null));
}
public byte[] compile(String className, String classContent) {
// Compile the code
JavaFileObject javaFileObject = new SourceCodeObject(className, classContent);
JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, diagnostics, null, null, List.of(javaFileObject));
Boolean result = task.call();
if (result == null || !result) {
throw new CompilationException(className, "Compilation failed.");
}
return fileManager.getBytecode();
}
public void load(String className, byte[] bytecode) {
dynamicClassLoader.loadClass(className, bytecode);
}
}
x
public class DynamicClassLoader extends ClassLoader {
public Class<?> loadClass(String className, byte[] bytecode) {
return defineClass(className, bytecode, 0, bytecode.length);
}
}
class ByteCodeGeneratingFileManager extends ForwardingJavaFileManager<JavaFileManager> {
private ByteArrayOutputStream bytecode;
ByteCodeGeneratingFileManager(JavaFileManager fileManager) {
super(fileManager);
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) {
return new ByteCodeObject(className, kind);
}
public byte[] getBytecode() {
return bytecode.toByteArray();
}
class ByteCodeObject extends SimpleJavaFileObject {
ByteCodeObject(String name, JavaFileObject.Kind kind) {
super(URI.create("bytecode:///" + name + kind.extension), kind);
}
@Override
public OutputStream openOutputStream() {
bytecode = new ByteArrayOutputStream();
return bytecode;
}
}
}
的一种或多种
我首先编译TestClassOne
的代码,然后使用编译器调用compiler.load("packOne.TestClassOne", byteCode)
来加载这个类。
问题是当我试图编译TestClassTwo
时。当我这样做时,我得到以下错误:error: package packOne does not exist
,就好像它不识别第一个生成的类一样。我使用了相同的编译器示例,因此也使用了相同的fileManager示例。
有没有人能告诉我为什么会发生这种情况,以及可能的解决办法是什么?
1条答案
按热度按时间qgelzfjb1#
当使用
-d
选项的目标目录时,你可以很容易地追溯到非动态编译的问题,例如在命令行上的javac
。在单独的通道中编译第二个类将找不到第一个通道中生成的类,除非你将输出目录添加到类路径。因为你没有把生成的类存储在一个目录中,所以你没有一个目录可以添加到类路径中。相反,你的自定义文件管理器必须准备好在编译器要求的时候提供存储的类文件。但是你的文件管理器有一个根本的问题,那就是它没有被设计成记住多个类文件。同样地,你的类装入器并没有准备好按需提供类。所以它只适用于你现在尝试的最简单的增量构建,但是,例如,仅仅有一个内部类就已经破坏了它。
因此,建议的步骤如下:
compile
方法,以便在源文件生成多个类文件的情况下,从文件管理器的Map中返回与所需类匹配的类文件getJavaFileForInput
检查类是否在生成的类文件中(和kind == CLASS
)以返回它(否则委托super
查找预定义的类)findClass
,同时检查Map中是否存在所请求名称的类文件,并定义和返回类(findClass
将仅为尚未定义的类调用)