对于一个注解处理器来说,声称要处理的注解集合为空,这是什么意思?
文档指出,如果一个处理器声称了一组注解,就不会再请求其他注解处理器去处理这组注解。
但似乎最终轮的注解处理(a)对任何之前被要求处理的处理器总是会发生,(b)总是拥有一个空的注解集合。难道这不违反规范吗?
假设处理器1在其 process 方法返回 true,当其 annotations 参数为空时,声称了空集合。根据文档,“后续处理器不会被请去处理”这个集合(在此情况为空)。但事实并非如此。我是不是理解错了?规范是否只在 processingOver 返回 false 的情形下才成立?
解决方案
你误解了规范。
“Annotation interface”指的是具体的注解,而不是类型。换句话说,给定两份源码:
@Foo
public class Example1 {}
@Foo
public class Example2 {}
那是两种可以被声称的独立注解。术语“注解接口”有点误导,但结合上下文,意思很清楚。
集合 的含义其实很简单:你声称你所接收到的每一个注解。因此,在你实现的:
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
你需要这样理解:
- 这里有一堆要你处理的注解。它们已经被放入一个集合。
- 请告诉我你是否愿意表示你已经“完成”了它们。注解需要完成的工作,无论是什么,只要你已经完成了,就返回
true,否则不要这么做。你只能对它们全部声称“完成”,不能只标记其中一半为“完成”。
你似乎把文档读成了集合的数学意义:比如说,如果某个处理流程被以 {FOO, BAR} 调用(不论你认为什么是FOO与 BAR),那么就不会再有其他处理器被调用来处理 {FOO, BAR},但它们可能会被调用来处理 {FOO, BAR, BAZ}。这种解释荒谬至极(完全没有意义),并且显然不正确。
你并没有声称“全部Foo”。你或许会认为结果归结为同样的事情,因为你的AP会在第一轮获得带有 @Foo 注解的所有类型,因此如果你返回true,就声称了它们全部,我们甚至可以说你已经声称了 Foo 这个概念本身——即该类型本身(而不是逐个声称你所接收到的每个单独注解)。
但这显然不是它的工作原理——注解也可能产生新的源文件,而这些源文件又可能带有 @Foo 的注解。如果在第一轮你接收到一组带有 @Foo 注解的源元素,并且你 return true;,那么在第二轮你仍然可能被传入通过 annotations 参数传递给你的更多注解——你会被调用来处理在第1轮中生成且带有 @Foo 注解的源类型。如果你在这一轮是 return false;,那么在第2轮你将得到第1轮所得到的类型以及在第1轮中生成且带有 @Foo 注解的所有类型的并集。
因此,证明这类声称是针对单个注解而非注解类型本身来考察的。
我应该就这个规范提交一个Bug吗?
不应该。
总之,英语并非正式语言。读者的理解在一定程度上是必需的,任何规范都需要。规范可以写得更清晰,但那样就会变成成千上万页的文档。举例来说,Java规范并不包含英语词典,也不链接到权威词典作为规范来源。尽管如此,javadoc的 process 方法中仍使用了“Processes”这个术语,但并没有真正解释清楚。
因此,直截了当地说:这并不是那种“正式”的规范。你需要具备一定基础理解的人来阅读,才能让规范有意义。如果一个规范即使在具备合理熟悉度的人阅读后仍然含糊不清,那才应该提交错误报告。如果一个规范能用更简明同时也更清晰的方式编写(这是双赢),你可以提交PR1。
问题在于,你的阅读方式给我留下一种“不合理”的印象:一个对AP体系有基本了解、也懂Java的人应该能看出这并非规范的本意。因此,这并不是规范文本的错(但鉴于英语本身在某种程度上的主观性,这种看法也是主观的。这也是我所说的‘不正式’的一部分:纯粹的客观、逻辑论证本身无法单独判定一个规范是“好”还是“坏”)。
思考API想要实现什么很重要。为了获得语义上的直觉:process 到底是什么意思?APIdoc中的 claim 一词又是什么意思?它的要点是什么?
显然,这一想法是两方面的:
1) 存在轮次系统。只要包含触发处理器X 的带注解的源文件仍然是轮次的一部分,想要处理X 的注解处理器就会持续被包含在这些轮次中吗?有时会这样:存在轮次是有原因的。如果你的注解处理器需要查看其他源内容来决定如何处理,那么未来的轮次可能会添加更多源内容,所以处理器X 现在还不能完成任务。但有时:不会这样。如果X 不依赖于此类内容,它就可以在第一轮完成工作(例如:生成一个JSON文件,包含该类本身的所有public static final常量,忽略超类中的同类内容——你不需要等其他AP:你在第一轮就知道自己需要的一切)。在后一种情况下,当你已经“完成了任务”后,持续被要求处理会很烦人,因此你可以在完成任务的那一刻就返回 true,之后就不会再被调用。
2) 注解处理可能并不便宜,且注解处理是有域名的:冲突不应该发生。两位处理器不可能都试图处理同一个注解 com.foo.whatever.Anno。这只有在Foo Incorporated的团队完全失灵时才会发生。这些都是带有域名的。因此,一旦某个注解被“完成”,就没有必要再浪费周期去请求其他处理器去检查它们。
在此背景下,你对集合的数学解读显得荒谬——在规范中花大量文字去解释到底是什么意思只是浪费空间,规范中的文字也并非免费。规范通常很难进行单元测试,一旦变得过分庞大,以致没有人能一次性读完并全部理解以发现不一致之处,就会变得无用。
事实上,缺乏明确语义的规范往往会变得“糟糕”(为降低主观性:这类规范的实现往往会以微妙但恼人的冲突、需要大量教程与学习曲线、以及因规范问题而引发的Bugs较多等特征而著称),如果有任何问题,那就是Annotation Spec的问题在于它没有花足够的时间用语义学术语来解释这个“声称”模型。一般来说,Javadoc并不太适合用于这方面——通常会在模块/包级别的Javadoc中给出。
不过,规范在某种程度上确实做到了。Processor 自身的类型级Javadoc已经涉及了相当多的内容。例如:
注意:如果一个处理器支持“*”并返回true,则所有注解都被声称。因此,用于实现额外有效性检查的通用处理器应返回false,以避免阻止其他此类检查器运行。
这显然与您的解释直接冲突。
[1] 不是说你可以那样做——只有OpenJDK共同体成员可以做这件事,你得向Azul等机构提交申请才行。