Java Compiler API具有彼此依赖的类

Java Compiler API with classes that depend on each other

我正在使用Java Compiler API编译内存中的类。也就是说,将类编译为字节码(没有.classes文件存储在磁盘中),然后通过重构字节码来加载。

有时,我需要编译一个依赖于另一个(也是在内存中编译的)类的类。例如:编译A类,然后编译依赖于A类的B类。

为解决此问题,我同时将A类和B类作为编译器API的getTask方法所需的编译单元。

但是,我真的不喜欢这种解决方案,因为它使我重新编译了已经编译的ClassA。

有没有办法解决这个问题?

编辑:我通过此链接找到了一种解决方案:http://www.ibm.com/developerworks/java/library/j-jcomp/index.html


是的,只要正确实现了ForwardingJavaFileManager,这是完全可能的。两个最重要的方法是inferBinaryName()和list()。如果正确设置了这两个类,则编译器将能够解析以前编译的类。

inferBinaryName()必须返回该类的简单名称(例如,com.test.Test的推断二进制名称仅为Test)。这是我的实现(JavaFileObject的子类称为InAppJavaFileObject):

1
2
3
4
5
6
7
8
9
@Override
public String inferBinaryName(Location location, JavaFileObject javaFileObject) {

    if(location == StandardLocation.CLASS_PATH && javaFileObject instanceof InAppJavaFileObject) {
        return StringUtils.substringBeforeLast(javaFileObject.getName(),".java");
    }

    return super.inferBinaryName(location, javaFileObject);
}

请注意,我将从结尾处剥离" .java"。构造JavaFileObject时,文件名必须以" .java"结尾,但是如果以后不去掉后缀,编译器将找不到您的类。

list()有点复杂,因为您必须小心与委托文件管理器配合使用。在我的实现中,我保留了一个完全合格的类名到JavaFileObject的子类的映射,可以对其进行迭代:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Override
public Iterable<JavaFileObject> list(Location action, String pkg, Set<JavaFileObject.Kind> kind, boolean recurse) throws IOException {

    Iterable<JavaFileObject> superFiles = super.list(action, pkg, kind, recurse);

    // see if there's anything in our cache that matches the criteria.
    if(action == StandardLocation.CLASS_PATH && (kind.contains(JavaFileObject.Kind.CLASS) || kind.contains(JavaFileObject.Kind.SOURCE))) {

        List<JavaFileObject> ourFiles = new ArrayList<JavaFileObject>();
        for(Map.Entry<String,InAppJavaFileObject> entry : files.entrySet()) {
            String className = entry.getKey();
            if(className.startsWith(pkg) && ("".equals(pkg) || pkg.equals(className.substring(0, className.lastIndexOf('.'))))) {
                ourFiles.add(entry.getValue());
            }
        }

        if(ourFiles.size() > 0) {
            for(JavaFileObject javaFileObject : superFiles) {
                ourFiles.add(javaFileObject);
            }

            return ourFiles;
        }
    }

    // nothing found in our hash map that matches the criteria...  return
    // whatever super came up with.
    return superFiles;
}

一旦正确实现了这些方法,其余方法就可以正常工作。尽情享受吧!


这引出了一个显而易见的问题,即为什么要首先单独编译A类。为什么不一口气编译所有内容?


我认为您不能避免同时编译这两个类。实际上,如果您不同时编译它们两者,则最终可能会遇到二进制兼容性问题或内联常数不正确的问题。

与从命令行编译一个类而不编译另一个类时遇到的问题本质上是相同的。

但是,老实说,我不会担心尝试像这样优化编译。 (并且,如果您的应用程序需要能够动态编译一个类而不是另一个,则可能存在重大的设计问题。)


如何保持文件的修改时间和(内存中的)已编译字节码?