注释处理,RoundEnvironment.processingOver()
在阅读 Java 中的自定义注释处理器的代码时,我注意到处理器process
方法中的这段代码:
@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!roundEnv.errorRaised() && !roundEnv.processingOver()) {
processRound(annotations, roundEnv);
}
return false;
}
碰巧我也在使用自定义注释处理器,所以我想在我的注释处理器中使用上面的代码段。
我以这种方式尝试了上面的代码:
if (!roundEnv.errorRaised() && !roundEnv.processingOver()) { processRound(annotations, roundEnv);
}
return false;
& 这条路:
if (!roundEnv.errorRaised()) { processRound(annotations, roundEnv);
}
return false;
但我没有注意到处理器行为的任何变化。我得到了!roundEnv.errorRaised()
支票,但看不到有!roundEnv.processingOver()
什么用。
我想知道roundEnv.processingOver()
在处理特定回合时有用的用例。
回答:
这两项检查都很重要,但是直到在同一项目中一次运行多个注释处理器,您才会注意到它们的效果。让我解释。
当Javac由于任何原因(例如由于缺少类型声明或解析错误)而使编译失败时,它不会立即终止。相反,它将收集有关错误的尽可能多的信息,并尝试以有意义的方式向用户显示该信息。另外,如果有注释处理器,并且错误是由缺少类型或方法声明引起的,则Javac将尝试运行这些处理器并重试编译,以期它们生成丢失的代码。这称为“多轮编译”。
编译顺序如下所示:
- 初级轮次(可能与代码生成有关);
- 几轮可选的代码生成;新的回合将发生,直到注释处理器什么都不会产生为止;
- 最后一轮;在此回合期间生成的代码将不会进行注释处理。
每一轮都是对代码进行全面的尝试。除最后一轮外,每一轮都将重新运行代码上的每个注释处理器,这些代码先前是由注释处理器生成的。
这个美妙的序列允许使用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
:
- 如果您的处理器生成了新的公开可见的类(可以如上所述在用户代码“提前”中使用),则您必须超级灵活:继续生成,在可能的情况下忽略丢失的位和不一致之处,并
errorRaised
。这样可以确保在Javac继续进行错误报告时,您会尽可能少地丢失一些东西。 - 如果您的代码未生成新的公开可见的类(例如,因为它仅创建程序包私有的类,而其他代码将在运行时反射性地查找它们,请参见ButterKnife),那么您应
errorRaised
尽快检查并在退出时立即退出返回true。这将简化您的代码并加快错误编译的速度。
为processingOver
:
- 如果当前回合不是最后一回(
processingOver
返回false),则尝试生成尽可能多的输出;否则,返回0。忽略用户代码中缺失的类型和方法(假设其他注释处理器可能在随后的回合中生成它们)。但是在其他注解处理器可能需要的情况下,仍要尝试尽可能多地生成。例如,如果在每个类上触发了用注释的代码生成@Entity
,则应迭代这些类并尝试为每个类生成代码,即使先前的类有错误或缺少方法也是如此。就个人而言,我只是将每个单独的生成单元都包装在try-catch中,并检查processingOver
:如果为假,请忽略错误并继续遍历注释并生成代码。这样,Javac可以通过运行它们直到完全满足为止,从而打破由不同注释处理器生成的代码之间的循环依赖关系。 - 如果当前回合不是最后一回(
processingOver
返回false),并且未处理某些先前回合的注释(由于异常导致处理失败,我记得它们),请重试这些注释。 - 如果当前回合是最后一回(
processingOver
返回true),请检查是否还有未处理的注释。如果是这样,则编译失败(仅在 上 一轮!)
上面的序列是 预期的 使用方式processingOver
。
一些注释处理器的用法processingOver
有些不同:它们缓冲在每一轮中生成的代码,并Filer
在最后一轮中实际将其写入。这样可以解决对其他处理器的依赖关系,但可以防止
其他 处理器查找“小心”处理器生成的代码。这是一个讨厌的策略,但是如果生成的代码不打算在其他地方引用,我想它还可以。
还有注释处理器,例如上述的第三方Spring配置验证器:它们误解了某些东西,并以猴子和扳手的方式使用API。
为了更好地理解整个问题,请安装Dagger2,并尝试在类中引用Dagger生成的类,这些类由另一个注释处理器使用(最好以某种方式使该处理器解析它们)。这将迅速向您显示这些处理器如何处理多轮编译。大多数都会使Javac异常崩溃。有些会吐出成千上万的错误,填充IDE错误报告缓冲区并混淆编译结果。很少有人会正确地参与多轮编译,但是如果失败,仍然会吐出很多错误。
“尽管存在错误仍保留生成代码”部分专门用于减少编译失败期间报告的编译错误数。更少的类丢失=希望的声明错误更少。或者,不要创建注释处理器,它会诱使用户引用由它们生成的代码。但是,您仍然必须应对这种情况,当某些注释处理器生成的代码带有注释时,与“提前”声明不同,用户期望开箱即用。
回到最初的问题:由于不应期望Spring配置验证处理器生成任何代码(希望我没有深入研究它),但是应该始终报告已扫描配置中的所有错误,因此理想情况下,它应该像这样工作:忽略errorRaised
并推迟配置扫描,直到processingOver
返回true:这将避免在多次编译回合中多次报告相同的错误,并允许注释处理器生成新的配置文件。
令人遗憾的是,所讨论的处理器看起来已经废弃了(自2015年以来没有提交),但是作者活跃在Github上,因此也许您可以向他们报告该问题。
同时,我建议您向经过深思熟虑的注释处理器学习,例如Google
Auto,Dagger2或我的小型研究项目。
以上是 注释处理,RoundEnvironment.processingOver() 的全部内容, 来源链接: utcz.com/qa/419701.html