如何确保注释处理器始终应用于所有带注释的元素?

问题描述

我编写了自定义注释处理器,它收集所有带注释的类,按字典顺序组织它们并为每个带注释的类生成一个新类。

在 Intellij Idea 中,当项目以增量方式构建时,并非所有 来自项目的带注释的类都会传递给我的注释处理器,只有那些已被修改/添加的类 . 这违反了排序逻辑。

如何确保注释处理器始终应用于每个构建中的所有注释元素?

我也找到了这篇文章,但似乎它只适用于 Gradle:https://docs.gradle.org/current/userguide/java_plugin.html#sec:incremental_annotation_processing

是否可以让注释处理器为任何构建工具聚合增量?

是否可以制作这样的注解处理器来隔离增量?

我的注释处理器的源代码https://github.com/ElegantNetworking/ElegantNetworkingAnnotationProcessor

解决方法

你问错了问题。以下是导致您提出错误问题的思考过程:

  1. 嗯,我的 AP 在编译运行时只能看到少数所有源代码,这很奇怪!这会导致错误,我想修复这些错误。
  2. 哦,等等,我明白了,这是因为增量编译。
  3. 我知道!我将禁用增量编译!
  4. 嗯,那我该怎么做?我最好问一下。

我会先给出一个直接的答案,但你不会喜欢它:你基本上不能。每次系统想要编译时重新编译整个代码库的效率非常低;当源文件中的单个简单更改导致必须等待 250 秒才能看到此效果时,没有人会喜欢它。您很快就会指责这些工具(无论是 gradle 还是 intellij)对您的工作时间非常不利。工具知道这一点,并且不会(很容易)允许这种无辜的行为(例如包含一些注释处理器)使工具无法使用。

你也不想知道如何“修复”这个问题,因为,好吧,我只是说“边界无法使用”。您肯定不希望将更改的周转时间从半秒缩短到 5 分钟。

虽然是一个很好的解决方案 - 但前提是你退后几步。

关于增量编译的事情是:没有被编译的东西(因为它们没有改变/不需要改变)?它们WERE 较早编译。您需要做的就是继续:就像编译源文件会产生一个“持久”的结果一样,这意味着您不需要重做,直到出现某些表明您需要重新应用该过程的情况,您需要对你的 AP 做同样的事情:如果你的 AP 处理了一些源文件,那需要留下持久的效果;这种效果需要足以满足所有未来的运行,而没有原始源树的好处,至少在更改所述源树之前是这样。

这比看起来容易,因为你有文件管理器。

我将以注解处理器为例:

这个处理器将扫描所有用 @Provides(com.pkg.Foo.class) 注释的类型,检查这样注释的类型是否实现或扩展了 Foo,然后创建一个文件 META-INF/services/com.pkg.Foo,在那里列出类型.这准确地描述了 SPI 处理器的工作原理:例如,这就是 google's auto-service processor 的作用(周围有很多这样的项目)。

对于完整的编译运行来说,这个过程是微不足道的:AP 可以只制作一个 Map<String,List<String>> 来映射,例如"com.pkg.Foo"["com.company.FooImpl1","com.company.FooImpl2"],在轮次发生和源文件被访问时填充它,然后在最后一轮时,以服务文件的形式转储这些映射。 AP 就像 2 页的代码,几乎微不足道,但非常有用。

问题是,当增量编译发生时,该模型实际上并不适用:在增量编译运行中,只有 FooImpl1 被发现,因此映射仅将 Foo 映射到 FooImpl1 ,当需要将文件从磁盘中转储时,FooImpl2 刚刚从您的服务文件中消失,即使 FooImpl2 类仍然存在 - 它根本不在增量编译运行中,因为它没有改变.

不过,解决方案很简单:您有一个文件管理器!

您需要先阅读服务文件,而不是将这些构建的地图中的每一个都转储到服务文件中并开始使用。如果它不存在,很容易,只需返回“转储列表”代码即可。但是如果它,请阅读其中的每个条目,向文件管理器询问这些类。如果文件管理器找不到其中之一,则从服务文件中删除该行。如果可以,请保留它。

好的,所以现在我们的 AP 已经从 2 页变成了 3 页,但它现在完全能够跟随增量编译。它可以区分某人删除 FooImpl2 并进行完整重新编译(这将导致仅包含 FooImpl1 的服务文件),以及某人首先进行完整运行(导致 1 和 2 都被在服务文件中),然后只更改 FooImpl1.java 并执行增量编译运行:

class MyProcessor extends javax.annotation.processing.AbstractProcessor {
  @Override public void init(ProcessingEnvironment env) {
    // you need these:
    Filer filer = env.getFiler();
    Elements elementUtils = processingEnv.getElementUtils();
  }
}

使用文件管理器,您可以:

  FileObject resource = filer.getResource(StandardLocation.CLASS_OUTPUT,"",pathToServicesFile);

然后您可以从那里读取该文件(如果存在),以检查该服务文件中已经存在哪些类:在增量编译运行中,这将为您提供 com.company.FooImpl1com.company.FooImpl2 .然后您可以检查这些类型(仍然)是否存在:

elements.getTypeElement("com.company.FooImpl1")

如果返回 null,则它不再存在,您可以从服务文件中删除它。如果是,请保留它 - 除非您在循环中点击该文件并且结果它不再被注释。关键是:如果在你的回合中你根本没有命中那个文件,这意味着它被排除在外,因为增量编译过程没有认为它发生了变化,因此,最后的已知状态(即 FooImpl1实现 Foo 并用 @Provides(Foo.class) 注释,因此为什么它在已经存在的服务文件中)仍然是正确的,因此,采取相应的行动。

如果您的注释处理器的输出/效果不包括任何可能用于在以后的增量编译运行中计算出来的东西,那么制作这样的文件:制作一个文件,'跟踪“您确实需要了解的状态。