2012-11-15 51 views
12

我有两个类与方法,我想将两个类的方法合并到一个类。AnnotationProcessor使用多个源文件创建一个文件

@Service("ITestService") 
public interface ITest1 
{ 
    @Export 
    void method1(); 
} 

@Service("ITestService") 
public interface ITest2 
{ 
    @Export 
    void method2(); 
} 

的结果应该是:

public interface ITestService extends Remote 
{ 
    void method1(); 
    void method2(); 
} 

我AnnotationProcessor的第一次运行产生正确的输出(因为RoundEnvironment同时包含类)。

但是,如果我编辑类之一(例如添加新的方法),RoundEnviroment仅包含所编辑的类,因此结果被follwing(添加newMethod()接口ITest1)

public interface ITestService extends Remote 
{ 
    void method1(); 
    void newMethod(); 
} 

现在method2丢失。我不知道如何解决我的问题。有没有办法(Enviroment),来访问项目中的所有课程?还是有另一种方法来解决这个问题?

生成这个类的代码很长,所以这里简要说明我如何生成这个类。我通过与env.getElementsAnnotatedWith(Service.class)元素迭代并提取方法,并将其写入新文件:

FileObject file = null; 
file = filer.createSourceFile("com/test/" + serviceName); 
file.openWriter().append(serviceContent).close(); 
+0

你在Eclipse中运行这个批注处理器吗? –

+2

@ johncarl的观点很重要,必须是真实的。标准的Java编译器不允许增量编译。 RoundEnvironment没有办法只能包含一个文件。 Eclipse编译器是增量式的,只编译已更改的文件。看起来这个逻辑对你来说不起作用,你需要通过Eclipse向一个给定的文件重新编译。可能有些事情我们可以尝试,但首先,为了避免浪费精力,我们应该确定这只会影响Eclipse编译。 – Pace

+1

@Pace:是否指定了某处javac从不使用增量编译?我认为ant和maven也有增量编译的模式,所以我猜他们也不能正确使用这样的注释处理器。 –

回答

7

- 选项1 - 命令行手动编译---

我试图做什么你想要从处理器访问所有类,并且正如人们所评论的那样,javac始终在编译所有类,并且从RoundEnvironment我可以访问所有正在编译的类,每次(即使没有文件更改时),只有一个细节:只要所有类都显示在要编译的类的列表中。

我已经做了一些测试有两个接口,其中一个(A)取决于(B)等(延伸),我有以下情况:

  1. 如果我要求编译器编译明确只有具有依赖关系(A)的接口,将java文件的完整路径传递到命令行,并将输出文件夹添加到类路径,只有传入命令行的接口才会被处理。
  2. 如果我只显式编译(A)并且不将输出文件夹添加到类路径中,编译器仍然只处理接口(A)。但它也给了我警告:Implicitly compiled files were not subject to annotation processing.
  3. 如果我使用*或将两个类传递给编译器到命令行,那么我会得到预期的结果,两个接口都会得到处理。

如果您将编译器设置为冗长,您将收到一条显示消息,显示您将在每一轮中处理哪些类。这是我得到了什么,当我明确地传递接口(A):

Round 1: 
input files: {com.bearprogrammer.test.TestInterface} 
annotations: [com.bearprogrammer.annotation.Service] 
last round: false 

这是我有什么,当我加入这两个类:

Round 1: 
input files: {com.bearprogrammer.test.AnotherInterface, com.bearprogrammer.test.TestInterface} 
annotations: [com.bearprogrammer.annotation.Service] 
last round: false 

在这两种情况下,我看到了编译器解析这两个类,但以不同的顺序。对于第一种情况(仅添加一个接口):

[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\TestInterface.java]] 
[parsing completed 15ms] 
[search path for source files: src\main\java] 
[search path for class files: ...] 
[loading ZipFileIndexFileObject[lib\processor.jar(com/bearprogrammer/annotation/Service.class)]] 
[loading RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]] 
[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]] 

对于第二种情况(添加所有接口):

[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\AnotherInterface.java]] 
... 
[parsing started RegularFileObject[src\main\java\com\bearprogrammer\test\TestInterface.java]] 
[search path for source files: src\main\java] 
[search path for class files: ...] 
... 

重要这里的细节是,编译器加载的依赖作为隐式在第一种情况下编译的对象。在第二种情况下,它将把它作为待编译对象的一部分加载(你可以看到这一点,因为它在提供的类被解析后开始搜索文件的其他路径)。看起来,隐式对象不包含在注释处理列表中。请参考Compilation Overview。其中没有明确说明要处理哪些文件。

在这种情况下,解决方案将始终将所有类添加到编译器的命令中。

---选项2 - 从Eclipse的编译---

如果从Eclipse的编译,增量构建会让你的处理器失败(没有测试过)。但我认为你可以绕过那个要求一个干净的构建(Project> Clean ...,也没有测试过)或者编写一个总是清理classes目录并设置一个Ant Builder from Eclipse的Ant构建。

---选项3 - 使用构建工具---

如果您使用的是像蚂蚁,Maven的或摇篮一些其他的构建工具,最好的解决办法是在比单独的步骤中源代你的汇编。你还需要让你的处理器在一个单独的前一步中编译(或者如果使用Maven/Gradle构建多项目,则需要单独的子项目)。

  1. 对于处理步骤,你总是可以做一个全面清理“汇编”,而不实际编译代码(使用选项-proc:only从javac的只处理的文件)
  2. 附:这是因为是最好的方案生成的源代码就位,如果您使用的是Gradle,那么如果它们没有改变,就足够聪明,不会重新编译生成的源文件。 Ant和Maven只会重新编译所需的文件(生成的文件和它们的依赖文件)。

对于第三个选项,您还可以设置一个Ant构建脚本,以从Eclipse中生成这些文件作为在Java构建器之前运行的构建器。在一些特殊的文件夹中生成源文件并将其添加到Eclipse中的类路径/构建路径中。

+0

不错的答案,如果你可以找到一些官方的链接,它说javac没有增量编译你已经赢得了赏金。 –

+0

我不认为规范要求编译器是增量的或不是。这似乎是编译器实现者的决定。 在Filer javadoc(http://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Filer.html)中,甚至有关于增量或非增量的引用,并没有将其明确化,暗示它确实取决于实施。 “这些信息可能会在增量环境中使用,以确定是否需要重新运行处理器或删除生成的文件。非增量环境可能会忽略原始元素信息。” – visola

+0

@JörnHorstmann在阅读了很多关于它之后,我的结论是Java是关于语言和JVM的。编译器没有规范。该规范仅解释字节码的格式。如果你想手动生成字节码并使用笔记本和笔处理注释,那很好!只要您尊重用于注释处理的API并在最后生成正确的字节码格式。所以实际上这个实施需要增量或不增量。一个简单的例子是Eclipse编译器,它也生成标准字节码,但是是增量式的。 – visola

2

NetBeans @Messages注释会为同一个包中的所有类生成单个Bundle.java文件。它的渐进式编译感谢与正常工作到下面的技巧在annotation processor

Set<Element> toProcess = new HashSet<Element>(); 
for (Element e : roundEnv.getElementsAnnotatedWith(Messages.class)) { 
    PackageElement pkg = findPkg(e); 
    for (Element elem : pkg.getEnclosingElements()) { 
    if (elem.getAnnotation(Message.class) != null) { 
     toProcess.add(elem); 
    } 
    } 
} 
// now process all package elements in toProcess 
// rather just those provided by the roundEnv 

PackageElement findPkg(Element e) { 
    for (;;) { 
    if (e instanceof PackageElement) { 
     return (PackageElement)e; 
    } 
    e = e.getEnclosingElement(); 
    } 
} 

通过这样做,可以肯定包中的所有(顶层)的元素被一起处理,即使编译只被调用上包中的单个源文件。

如果您知道在哪里查找注释(包中的顶层元素或者包中的任何元素),您应该始终能够获取所有这些元素的列表。

+0

这对我不起作用,第一个循环pkg.getEnclosedElements()返回元素,但注释仅由正在编译的元素返回,另一个getAnnotation返回null,尽管它们已经在类上定义了它。 (我在上面的链接中检查了注释处理器,并添加了if(roundEnv.processingOver())返回false;)编辑:我通过javac – RookieGuy