You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaCompiler无法识别动态加载类的问题求助

问题原因

Java编译器在编译阶段解析依赖时,不会自动从已加载的ClassLoader中查找类,而是通过JavaFileManager来查找源文件或字节码文件。你的实现存在两个关键问题:

  1. 仅将TestClassOne加载到了DynamicClassLoader,但这个类只在JVM运行时可访问,编译阶段的编译器无法感知到它。
  2. 你的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 16:05:02