JavaCompiler无法识别动态加载类的问题求助
问题原因
Java编译器在编译阶段解析依赖时,不会自动从已加载的ClassLoader中查找类,而是通过JavaFileManager来查找源文件或字节码文件。你的实现存在两个关键问题:
- 仅将
TestClassOne加载到了DynamicClassLoader,但这个类只在JVM运行时可访问,编译阶段的编译器无法感知到它。 - 你的
ByteCodeGeneratingFileManager只实现了输出字节码的逻辑,没有覆盖getJavaFileForInput方法——当编译器需要查找packOne.TestClassOne时,默认会去文件系统或classpath中查找,自然找不到内存中生成的类。
解决办法
需要让JavaFileManager能够返回已编译类的字节码对象,供编译器在编译阶段查找依赖。具体步骤如下:
1. 新增已编译类缓存
在Compiler类中维护一个缓存,存储已编译类的全限定名和对应字节码,方便后续编译时查找。
2. 修改ByteCodeGeneratingFileManager
覆盖getJavaFileForInput方法,当编译器请求依赖类时,从缓存中取出对应的字节码并返回一个可提供输入流的JavaFileObject。
修改后的完整代码示例
Compiler类
import javax.tools.*; import java.io.IOException; import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; public class Compiler { private final JavaCompiler javaCompiler; private final DynamicClassLoader dynamicClassLoader; private final DiagnosticCollector<JavaFileObject> diagnostics; private final ByteCodeGeneratingFileManager fileManager; private final Map<String, byte[]> compiledClasses = new HashMap<>(); public Compiler(DynamicClassLoader dynamicClassLoader) { this.dynamicClassLoader = dynamicClassLoader; this.javaCompiler = ToolProvider.getSystemJavaCompiler(); this.diagnostics = new DiagnosticCollector<>(); this.fileManager = new ByteCodeGeneratingFileManager( javaCompiler.getStandardFileManager(diagnostics, null, null), compiledClasses ); } public byte[] compile(String className, String classContent) { 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) { // 打印详细诊断信息,方便调试 diagnostics.getDiagnostics().forEach(diagnostic -> System.err.printf("编译错误 [%d行]: %s%n", diagnostic.getLineNumber(), diagnostic.getMessage(null)) ); throw new CompilationException(className, "Compilation failed."); } byte[] bytecode = fileManager.getBytecode(); // 编译成功后将字节码存入缓存 compiledClasses.put(className, bytecode); return bytecode; } public void load(String className, byte[] bytecode) { dynamicClassLoader.loadClass(className, bytecode); } // 补充SourceCodeObject实现(用于提供内存中的源码) private static class SourceCodeObject extends SimpleJavaFileObject { private final String code; SourceCodeObject(String className, String code) { super(URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); this.code = code; } @Override public CharSequence getCharContent(boolean ignoreEncodingErrors) { return code; } } // 自定义编译异常 static class CompilationException extends RuntimeException { public CompilationException(String className, String message) { super(String.format("类%s编译失败: %s", className, message)); } } }
ByteCodeGeneratingFileManager类
import javax.tools.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.Map; class ByteCodeGeneratingFileManager extends ForwardingJavaFileManager<JavaFileManager> { private ByteArrayOutputStream bytecode; private final Map<String, byte[]> compiledClasses; ByteCodeGeneratingFileManager(JavaFileManager fileManager, Map<String, byte[]> compiledClasses) { super(fileManager); this.compiledClasses = compiledClasses; } @Override public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { bytecode = new ByteArrayOutputStream(); return new ByteCodeObject(className, kind); } // 覆盖该方法,让编译器能从缓存中查找已编译的类 @Override public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException { if (kind == JavaFileObject.Kind.CLASS && compiledClasses.containsKey(className)) { // 返回缓存中的字节码对应的JavaFileObject return new ByteCodeInputObject(className, compiledClasses.get(className)); } // 缓存中没有则调用父类逻辑,去默认位置查找 return super.getJavaFileForInput(location, 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 ByteArrayOutputStream openOutputStream() { return bytecode; } } // 用于提供已编译类的输入流 class ByteCodeInputObject extends SimpleJavaFileObject { private final byte[] bytecode; ByteCodeInputObject(String className, byte[] bytecode) { super(URI.create("bytecode:///" + className.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS); this.bytecode = bytecode; } @Override public InputStream openInputStream() { return new ByteArrayInputStream(bytecode); } } }
DynamicClassLoader类(无需修改)
public class DynamicClassLoader extends ClassLoader { public Class<?> loadClass(String className, byte[] bytecode) { return defineClass(className, bytecode, 0, bytecode.length); } }
关键逻辑说明
compiledClasses缓存:保存所有已成功编译的类的字节码,确保后续编译时能快速找到依赖。getJavaFileForInput覆盖:当编译器解析TestClassTwo中的packOne.TestClassOne时,会调用该方法,我们从缓存中取出对应的字节码,返回一个能提供输入流的ByteCodeInputObject,编译器就能正确解析这个依赖类了。- 编译后存入缓存:每次编译成功后,将字节码存入缓存,保证后续编译的类可以依赖它。
内容的提问来源于stack exchange,提问作者Petre Popescu
相关产品推荐
相关产品推荐

