Java 注解处理器怎么避免重复生成:Filer、RoundEnvironment 与增量编译排查
Java 注解处理器第一次跑通时用着顺手,第二次接入增量编译却常常报 FilerException:同一个源文件明明已经生成过内容,处理器又试图重复写入。这个问题基本不是编译器随机抽风重复调用,多半是开发阶段没把处理轮次、来源元素和输出文件名当成一个整体来设计导致的。
process可能经历多轮,最后一轮没有待处理注解时也可能被调用。RoundEnvironment.processingOver()为真时只做收尾,不再创建新的源文件。Filer.createSourceFile的完整限定名必须稳定且唯一,重复创建应被视为设计错误。- 把“发现元素”和“生成文件”分开,再用来源元素、输出集合和 clean build 复查增量编译行为。
先看清注解处理器为什么会被调用多轮
JSR 269 的处理模型不是“扫描一次就结束”。编译器把源文件中的根元素交给处理器;处理器生成的新源文件可能在下一轮再次成为输入。官方 Processor.process 文档还特别说明:如果处理器被请求参与某轮,后续轮次仍可能调用它,包括最后一轮没有注解的情况。
@SupportedAnnotationTypes("demo.AutoDto")
@SupportedSourceVersion(SourceVersion.RELEASE_25)
public final class AutoDtoProcessor extends AbstractProcessor {
@Override
public boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
for (Element element : roundEnv.getElementsAnnotatedWith(AutoDto.class)) {
// 只在这里收集生成计划,稍后统一写文件
}
return true;
}
}
所以,看到同一个元素在日志里出现两次,先不要随便把集合改成“全局只跑一次”的粗暴逻辑。正确做法是确认每一轮的输入和结束标记,再判断输出是否已经存在。

Filer 的文件名就是生成协议的一部分
Filer 负责让处理器创建新的源文件、类文件或辅助资源。以生成 UserDto 为例,输出名应由包名和类型名稳定计算,而不是拼接时间戳或随机后缀:
String packageName = elementUtils.getPackageOf(typeElement)
.getQualifiedName()
.toString();
String generatedName = packageName + "." + typeElement.getSimpleName() + "Dto";
JavaFileObject file = processingEnv.getFiler()
.createSourceFile(generatedName, typeElement);
try (Writer writer = file.openWriter()) {
writer.write("package " + packageName + ";\n");
writer.write("public record " + typeElement.getSimpleName() + "Dto() {}\n");
}
把 typeElement 作为 originating element 传给 createSourceFile,能让工具链知道生成文件由哪个源元素产生。它不允许同一个处理器或另一个处理器再次创建相同限定名;重复写入时抛出的 FilerException 是重要的报错信号。
| 症状 | 优先检查 | 修复方向 |
|---|---|---|
| 同名文件已存在 | 生成名是否含随机值、处理轮次是否重复 | 固定限定名,结束轮次不再写入 |
| 没有生成任何文件 | 支持的注解名和元素筛选 | 核对 @SupportedAnnotationTypes 与 getElementsAnnotatedWith |
| 增量编译结果残留 | 旧 generated-sources 目录和 clean build 差异 | 先清理输出,再比较同一输入的生成结果 |
把发现、去重和生成拆成三个阶段
实际项目里更建议把处理器的主体拆开:第一步只从 RoundEnvironment 收集元素,第二步用完整限定名去重,第三步统一交给一个生成器写入。这样日志可以直接定位“重复来自输入,还是重复来自输出”。
Setplanned = new LinkedHashSet(); for (Element element : roundEnv.getElementsAnnotatedWith(AutoDto.class)) { TypeElement type = (TypeElement) element; String output = outputName(type); if (!planned.add(output)) { processingEnv.getMessager().printMessage( Diagnostic.Kind.NOTE, "skip duplicate: " + output, element); continue; } generate(type, output); }
这个集合只能解决当前轮次内的重复计划,不能替代稳定的源文件命名,也不能覆盖多个处理器之间的协作。若多个处理器都写同一个类型,应重新划分职责,或指定一个处理器拥有该输出文件的写入权限。
最后一轮和增量编译怎么验收
- 在
process开头记录轮次、注解集合数量和processingOver()值。 - 当
processingOver()为真时只输出统计或诊断信息,不调用Filer.createSourceFile。 - 用一次 clean build 生成基线,再只改业务源文件,比较 generated-sources 目录是否只发生必要变化。
- 人为让两个输入映射到同一个输出名,确认日志能定位来源元素,而不是只打印“已存在”。

常见问题
为什么最后一轮还会调用 process?
处理工具需要通知处理器处理阶段已经结束。最后一轮可能没有新的注解元素,所以必须先检查 processingOver(),不能按“有调用就生成”的逻辑处理。
FilerException 是不是只能删目录解决?
删目录只能清理一次残留,不能修复重复生成协议。应先确认完整限定名、处理轮次和多个处理器之间是否写了同一个文件。
为什么要传 originatingElements?
它把生成文件和来源元素关联起来,便于工具链追踪来源、报告诊断并处理增量编译。它不是防重复开关,但缺少它会让排查难度大幅上升。
处理器返回 true 还是 false?
如果当前处理器已经认领了所支持的注解,通常返回 true;返回 false 表示未认领,后续处理器仍可能获得这些注解。这个返回值影响处理器协作,不等于“本轮是否生成文件”。
注解处理器的稳定性,关键不在于把 process 写得更复杂,而在于让输入元素、处理轮次和输出文件名彼此可追踪。先用 clean build 建基线,再观察增量编译,重复生成问题通常很快就能落到具体的一行命名或轮次判断上。
PHP 8.4 非对称可见性 public private(set):只读接口与内部写入怎么拆
- 上一篇
- PHP 8.4 非对称可见性 public private(set):只读接口与内部写入怎么拆
- 下一篇
- Python dataclass 继承时字段顺序报错怎么拆:KW_ONLY、默认值与序列化边界
-
- 文章 · java教程 | 21小时前 | 并发编程 · Java教程 · java 并发锁 reentrantlock tryLock
- Java ReentrantLock tryLock 超时与资源释放
- 190浏览 收藏
-
- 文章 · java教程 | 1天前 | 文件上传 · Java教程 · java httpclient 流式上传 HttpRequest BodyPublisher
- Java HttpRequest BodyPublisher 实现流式上传
- 488浏览 收藏
-
- 文章 · java教程 | 1天前 |
- Java 模式匹配 switch 处理层级类型的穷尽性
- 301浏览 收藏
-
- 文章 · java教程 | 4天前 |
- Java 虚拟线程连接池改造的资源边界
- 236浏览 收藏
-
- 文章 · java教程 | 4天前 | Java · 虚拟线程 · java UncaughtExceptionHandler 虚拟线程 Thread.Builder.OfVirtual
- Java Thread.Builder.OfVirtual 设置线程异常处理器
- 139浏览 收藏
-
- 文章 · java教程 | 4天前 | 数据处理 · Java教程 · java windowFixed Stream Gatherer 事件窗口
- Java Stream Gatherer 组合短窗口事件的实现步骤
- 495浏览 收藏
-
- 文章 · java教程 | 5天前 | 并发 · Java · 随机数 · RandomGeneratorFactory Java随机算法 随机数并发
- Java RandomGeneratorFactory 怎么按能力选择随机算法
- 244浏览 收藏
-
- 文章 · java教程 | 5天前 |
- Java HexFormat 怎么在字节数组和十六进制文本间转换
- 361浏览 收藏
-
- 文章 · java教程 | 5天前 | 文件处理 · nio · Java教程 · java 文件比较 Files.mismatch 字节偏移
- Java Files.mismatch 怎么定位两个文件首个差异
- 342浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 312次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 370次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 364次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 333次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 155次使用
-
- Java try-with-resources 多个资源关闭顺序是什么
- 2026-09-10 501浏览
-
- 矩阵主副对角线快速定位技巧
- 2026-05-31 501浏览
-
- Java多态优化流程代码与行为分发改进
- 2026-05-26 501浏览
-
- JVM 类元数据双亲委派链表深度解析
- 2026-05-21 501浏览
-
- 反射异常处理:InvocationTargetException解析与应用
- 2026-05-16 501浏览

