注释处理,RoundEnvironment.processingOver()

时间:2017-12-12 18:38:36

标签: java annotation-processing java-annotations

在Java中读取custom annotation processor的代码时,  我在处理器的process方法中注意到了这段代码:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
  if (!roundEnv.errorRaised() && !roundEnv.processingOver()) {
    processRound(annotations, roundEnv);
  }
  return false;
}

碰巧我正在开发一个自定义Annotation处理器,&amp;我想在我的注释处理器中使用上面的代码段。

我用这种方式尝试了代码:

if (!roundEnv.errorRaised() && !roundEnv.processingOver()) {
    processRound(annotations, roundEnv);
}
return false;

&安培;这样:

if (!roundEnv.errorRaised()) {
    processRound(annotations, roundEnv);
}
return false;

但我没有注意到处理器行为的任何变化。 我得到!roundEnv.errorRaised()支票,但我看不出!roundEnv.processingOver()如何有用。

我想知道在处理某一轮时使用roundEnv.processingOver()非常有用的用例。

1 个答案:

答案 0 :(得分:16)

这两项检查都很重要,但在同一项目中同时运行多个注释处理器之前,您不会注意到它们的影响。让我解释一下。

当Javac因任何原因(例如由于缺少类型声明或解析错误)而导致编译失败时,它不会立即终止。相反,它将尽可能多地收集有关错误的信息,并尝试以有意义的方式向用户显示该信息。此外,如果有注释处理器,并且错误是由缺少类型或方法声明引起的,Javac将尝试运行这些处理器并重试编译,希望它们生成缺少的代码。这被称为&#34;多轮编译&#34;。

编译序列如下所示:

  1. 主要轮次(可能包含代码生成);
  2. 几个可选的代码生成轮;新的回合将发生,直到注释处理器没有生成任何内容;
  3. 最后一轮;在这一轮中生成的代码不会受到注释处理。
  4. 每轮都是编译代码的全面尝试。除最后一轮之外的每一轮都将重新运行代码上的每个注释处理器,这些处理器先前由注释处理器生成。

    这个精彩的序列允许使用像Dagger2和Android-Annotated-SQL这样的库推广的方法:在源代码中引用尚未存在的类,并让注释处理器在编译期间生成它:

    // this would fail with compilation error in absence of Dagger2
    // but annotation processor will generate the Dagger_DependencyFactory
    // class during compilation
    Dagger_DependencyFactory.inject(this);
    

    有些人认为这种技术是不合理的,因为它依赖于在源代码中使用不存在的类,并且将源代码与注释处理紧密联系起来(并且在IDE代码完成时不能很好地工作)。但这种做法本身是合法的,并且按照Javac开发人员的意图运作。

    那么,在你的问题中,所有这些与Spring的注释处理器有什么关系呢?

    TL; DR:你问题中的代码是错误的。

    使用这些方法的正确方式如下:

    代表errorRaised

    1. 如果您的处理器生成新的公开可见类(可以在用户代码和#34中使用;提前和#34;如上所述),您必须具有超弹性:继续生成,忽略丢失的位和不一致如果可能,忽略 errorRaised。这可以确保您在Javac进行错误报告狂欢时尽可能少地遗漏这些东西。
    2. 如果您的代码没有生成新的公开可见类(例如,因为它只创建包私有类,而其他代码将反映在运行时查找它们,请参阅ButterKnife),那么您应该检查{{1}尽快,如果返回true则立即退出。这将简化您的代码并加速错误的编译。
    3. 代表errorRaised

      1. 如果当前轮次不是最后一轮(processingOver返回false),请尝试生成尽可能多的输出;忽略用户代码中缺少的类型和方法(假设,其他一些注释处理器可能会在后续轮次中生成它们)。但是仍然尽可能地生成,以防其他注释处理器可能需要它。例如,如果在每个类上触发代码生成(使用processingOver注释),则应迭代这些类并尝试为每个类生成代码,即使先前的类有错误或缺少方法也是如此。就个人而言,我只是在try-catch中包装每个单独的生成单元,并检查@Entity:如果它是假的,忽略错误并继续迭代注释并生成代码。这使得Javac能够破坏代码之间的循环依赖关系,这些代理由不同的注释处理器生成,直到完全满意为止。
      2. 如果当前轮次不是最后一轮(processingOver返回false),并且之前的一些注释未被处理(我会在处理因异常而失败时记住它们),重试处理那些。
      3. 如果当前回合是最后一次(processingOver返回true),请查看是否仍有未处理的注释。如果是这样,则编译失败(仅在最后轮期间!)
      4. 上面的序列是使用processingOver预期方式。

        某些注释处理器稍微使用processingOver:它们缓冲每轮中生成的代码,并在上一轮中实际将其写入processingOver。这允许解析其他处理器的依赖性,但是阻止其他处理器查找由&#34;小心&#34;生成的代码。处理器。这是一个有点讨厌的策略,但如果生成的代码不打算在其他地方引用,我想这没关系。

        还有像上面提到的第三方Spring配置验证器这样的注释处理器:它们误解了一些东西,并以猴子和扳手的方式使用API​​。

        为了更好地了解整个事物,请安装Dagger2,并尝试在类中引用Dagger生成的类,由另一个注释处理器使用(最好以某种方式使该处理器解析它们)。这将很快向您展示这些处理器如何应对多轮编译。大多数人只会让Javac崩溃。有些人会吐出数千个错误,填充IDE错误报告缓冲区并混淆编译结果。很少有人能够正确参与多轮编译,但如果失败则仍然会发出很多错误。

        尽管存在错误,仍然会生成代码&#34; part特别用于减少编译失败期间报告的编译错误数。减少缺少类=减少缺少声明错误(希望如此)。或者,不要创建注释处理器,以煽动用户引用它们生成的代码。但是,当一些注释处理器生成代码时,您还需要处理情况,并使用您的注释进行注释 - 与#34;提前不同&#34;声明,用户将期望只是开箱即用。

        回到原始问题:由于Spring配置验证处理器不会生成任何代码(希望我没有深入研究它),但应该始终报告扫描配置中的所有错误,理想情况下应该像这样工作:忽略Filer并推迟配置扫描,直到errorRaised返回true:这将避免在多个编译轮次期间多次报告相同的错误,并允许注释处理器生成新的配置片段。

        可悲的是,有问题的处理器看起来已经放弃了(自2015年以来没有提交过),但是作者在Github上是活跃的,所以也许你可以向他们报告这个问题。

        与此同时,我建议您学习经过深思熟虑的注释处理器,例如Google Auto,Dagger2或my tiny research project