Java JavaCompiler.run() 也编译匿名 类

Java JavaCompiler.run() compiling anonymous classes as well

我正在尝试动态加载文本文件并编译它们。

File file = new File("Files/"+fileName+".java");
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
compiler.run(null, null, errStream, file.getAbsolutePath());

然后我将加载编译后的 .class 文件:

 public Class loadStrategyClass(File strategyClassFile) throws IOException
    {
        FileChannel roChannel = new RandomAccessFile(strategyClassFile, "r").getChannel();
        ByteBuffer buffer = roChannel.map(FileChannel.MapMode.READ_ONLY, 0, (int)roChannel.size());

        return defineClass(strategyClassFile.getName(), buffer, (ProtectionDomain)null);
    }

我目前 运行 有两个问题: 第一个是我加载的 .java 文件是否包含匿名 classes。 JavaCompiler class 似乎不会编译这些。 线程 "main" java.lang.IllegalAccessException 中的异常:Class Loader.ClassLoader 无法访问带有修饰符“class Files.myname.myclass$1 的成员”

第二个: 是不是有时我会收到 NoClassDefFoundError 的错误: 线程 "main" java.lang.NoClassDefFoundError 中的异常:Files/myname/myclass 尽管其他 classes 将正确加载并且 .class 文件位于该路径中。

显然,您的 loadStrategyClass 是在自定义 ClassLoader 中定义的。问题是为您感兴趣的 class 调用一次 defineClass 是不够的,您的 class 加载程序必须能够按需解析 classes,通常通过实现 findClass,因此 JVM 可以解决依赖关系,例如内部 classes.

您没有指定如何获取 loadStrategyClass 方法的 strategyClassFile 参数。由于您 运行 编译器没有任何选项,我想您只是查找了与源文件相关的文件。要解决其他依赖项,需要知道 class 目录的实际根目录。当您定义 class 文件的存储位置时,它会变得容易得多,例如

// customize these, if you want, null triggers default behavior
DiagnosticListener<JavaFileObject> diagnosticListener = null;
Locale locale = null;

JavaCompiler c = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fm
    = c.getStandardFileManager(diagnosticListener, locale, Charset.defaultCharset());

// define where to store compiled class files - use a temporary directory
Path binaryDirectory = Files.createTempDirectory("compile-test");
fm.setLocation(StandardLocation.CLASS_OUTPUT,
               Collections.singleton(binaryDirectory.toFile()));

JavaCompiler.CompilationTask task = c.getTask(null, fm,
    diagnosticListener, Collections.emptySet(), Collections.emptySet(),
    // to make this a stand-alone example, I use embedded source code
    Collections.singleton(new SimpleJavaFileObject(
        URI.create("string:///Class1.java"), Kind.SOURCE) {
            public CharSequence getCharContent(boolean ignoreEncodingErrors) {
                return "package test;\npublic class Class1 { public class Inner {} }";
            }
        }));
if(task.call()) try {
    URLClassLoader cl = new URLClassLoader(new URL[]{ binaryDirectory.toUri().toURL() });
    Class<?> loadedClass = cl.loadClass("test.Class1");
    System.out.println("loaded "+loadedClass);
    System.out.println("inner classes: "+Arrays.toString(loadedClass.getClasses()));
} catch(ClassNotFoundException ex) {
    ex.printStackTrace();
}

在上面的例子中,我们知道class目录的根目录,因为我们已经定义了它。这允许简单地使用现有的 URLClassLoader 而不是实现一种新型的 class 加载程序。当然,使用自定义文件管理器,我们也可以使用 in-memory 存储而不是临时目录。


您可以使用此 API 来发现已生成的内容,这使您可以使用生成的 class 而无需事先知道哪个包或内部 class 声明存在于您要编译的源文件。

public static Class<?> compile(
    DiagnosticListener<JavaFileObject> diagnosticListener,
    Locale locale, String sourceFile) throws IOException, ClassNotFoundException {

    JavaCompiler c = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fm
        = c.getStandardFileManager(diagnosticListener, locale, Charset.defaultCharset());

    // define where to store compiled class files - use a temporary directory
    Path binaryDirectory = Files.createTempDirectory("compile-test");
    fm.setLocation(StandardLocation.CLASS_OUTPUT,
                   Collections.singleton(binaryDirectory.toFile()));

    JavaCompiler.CompilationTask task = c.getTask(null, fm,
        diagnosticListener, Collections.emptySet(), Collections.emptySet(),
        fm.getJavaFileObjects(new File(sourceFile)));
    if(task.call()) {
        Class<?> clazz = null;
        URLClassLoader cl = new URLClassLoader(new URL[]{binaryDirectory.toUri().toURL()});
        for(JavaFileObject o: fm.list(
            StandardLocation.CLASS_OUTPUT, "", Collections.singleton(Kind.CLASS), true)) {

            String s = binaryDirectory.toUri().relativize(o.toUri()).toString();
            s = s.substring(0, s.length()-6).replace('/', '.');
            clazz = cl.loadClass(s);
            while(clazz.getDeclaringClass() != null) clazz = clazz.getDeclaringClass();
            if(Modifier.isPublic(clazz.getModifiers())) break;
        }
        if(clazz != null) return clazz;
        throw new ClassNotFoundException(null,
            new NoSuchElementException("no top level class generated"));
    }
    throw new ClassNotFoundException(null,
        new NoSuchElementException("compilation failed"));
}

如果您使用它来动态绑定插件或模块,您可以扩展搜索以查找实现特定接口或具有特定注释的结果class。