【问题标题】:Java Compiler API with classes that depend on each other具有相互依赖的类的 Java 编译器 API
【发布时间】:2011-12-07 21:07:12
【问题描述】:

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

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

为了解决这个问题,我将 Class A 和 Class B 作为编译器 API 的 getTask 方法所需的编译单元传递。

但是,我真的不喜欢这个解决方案,因为它让我重新编译已经编译的 Class A。

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

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

【问题讨论】:

  • 对不起,如果我的速度很慢,但我看不出您发布的链接如何解决问题。我自己也遇到过同样的问题……如果我可以单独编译类,我的 API 会变得更加简洁和易于管理。如果您可以将解决方案作为答案发布,我将不胜感激。 :)
  • 实际上,我刚刚解决了这个问题,所以为了后代,我已经发布了一个答案。谢谢你的好问题!

标签: java java-compiler-api


【解决方案1】:

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

inferBinaryName() 必须返回类'simple name(例如,com.test.Test 的推断二进制名称将只是 Test)。这是我的实现(我的JavaFileObject 子类称为InAppJavaFileObject):

@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 的子类,我可以对其进行迭代:

@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;
}

一旦您正确实施了这些方法,其余的就可以工作了。享受吧!

【讨论】:

  • 这里唯一需要注意的是,在您使用的任何自定义 JavaFileObject 中(在您的示例中为“InAppJavaFileObject”),您必须实现 openInputStream() 以返回存储在 openOutputStream() 返回的流中的任何字节,如果这是您在编译期间存储类数据的方式。但它对我来说非常有用,谢谢!
【解决方案2】:

这导致了一个明显的问题,即为什么要先单独编译 A 类。为什么不一次编译所有内容?

【讨论】:

  • 我认为你没有抓住重点。我发布了一个例子。您的解决方案实际上不可扩展或模块化。
  • @halfwarp:如果我没有抓住重点,那可能是因为你没有提供足够的信息?你没有解释你想要实现什么,也没有解释你需要什么样的可扩展性或模块化。
【解决方案3】:

如果你维护文件的修改时间和(内存中的)编译字节码怎么办?

【讨论】:

    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2015-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多