annotation processor适合做架构层静态检查,因为它是唯一能在java编译期稳定访问完整ast、类型信息和注解元数据的官方机制;通过roundenvironment精准筛选元素,用messager报error中断编译,结合supportedoptions动态控制规则,且需将自定义注解抽为独立api模块避免类加载失败。

为什么 AnnotationProcessor 适合做架构层静态检查
因为它是唯一能在 Java 编译期(javac 阶段)稳定访问完整 AST、类型信息和注解元数据的官方机制——比 Lombok 插件更底层,比 SpotBugs/Checkstyle 更贴近源码结构,且不依赖 IDE 或额外构建插件。团队规范如「Controller 不得直接 new Service」或「Data 类禁止继承」,必须在编译失败时拦截,而不是靠 Code Review 或 CI 后置扫描。
如何用 RoundEnvironment 定位违规类并报告错误
关键不是遍历所有类,而是精准筛选带特定注解(如 @Layer)或位于特定包路径下的元素,再结合 Element.getEnclosedElements() 和 Element.getSuperclass() 做语义判断。错误必须用 processingEnv.getMessager().printMessage() 报出 Diagnostic.Kind.ERROR,否则不会中断编译。
- 不要用
elements.getAllMembers()查方法调用——它不包含字节码级调用关系,只能查声明;想检查「new Service」得解析ExecutableElement的getParameters()和getSimpleName(),再递归扫描其getEnclosedElements()中的VariableElement初始化表达式(需配合TreePathScanner+JavacTrees,但注意:标准AnnotationProcessorAPI 不暴露 AST 树,得用com.sun.source.util.Trees并 castProcessingEnvironment - 包路径检查别硬编码字符串匹配,用
element.getEnclosingElement().toString().startsWith("com.example.controller")更可靠,避免子包误判 - 报错位置要精确到行号:传入
element或具体VariableElement,而非笼统的TypeElement
javax.annotation.processing.SupportedOptions 怎么控制检查开关和规则粒度
通过 -Acheck.layering=true -Aallow.service.impl.in.controller=false 这类编译参数注入配置,比写死在 processor 里更灵活。processor 内部用 processingEnv.getOptions().get("check.layering") 读取,值为 "true" 字符串而非布尔型,需显式转换。
- 选项名必须全小写加点号,IDEA 的 Maven 编译默认不透传
-A参数,需在Settings > Build > Compiler > Java Compiler中勾选「Use compiler: javac」并手动添加Additional command line parameters - Gradle 用户要在
compileJava.options.compilerArgs里追加,例如['-Acheck.domain.rules=true'],且确保annotationProcessor依赖已声明,否则选项被忽略 - 避免用选项控制「是否启用整个 processor」——JVM 会跳过未声明
@SupportedAnnotationTypes的 processor,应始终启用,靠选项决定是否执行具体校验逻辑
为什么不能只靠 @SupportedAnnotationTypes("*") 捕获所有类
通配符会导致 processor 被触发次数爆炸(每个源文件都进一轮),且无法区分「被注解的类」和「引用了注解的类」。真正需要检查的是架构约束涉及的实体(如 @Controller、@Repository 标记的类),应显式声明 @SupportedAnnotationTypes({"org.springframework.stereotype.Controller", "com.example.arch.DomainEvent"})。
- Spring 自带注解如
@Controller是@Target(ElementType.TYPE),可直接匹配;但若检查「Service 调用 Repository」,就得监听所有类(因调用发生在方法体内),此时需结合RoundEnvironment.getRootElements()+ 包过滤,而非依赖注解类型 - Android 环境下
androidx.annotation.NonNull等支持库注解可能被混淆或未参与编译,需确认 processor classpath 包含对应 jar,否则getTypeElement()返回 null - 增量编译时,
RoundEnvironment只返回变更的元素,若规则依赖跨类关系(如 A 类调用 B 类方法),必须缓存上一轮结果或改用全量扫描模式
最易被忽略的是 processor 的 classloader 隔离问题:自定义注解若放在被检查模块中,processor 默认看不到其 class —— 必须把注解定义抽成独立的 api 模块,并作为 processor 的 compileOnly 依赖,否则 getTypeElement("com.example.Layer") 返回 null,整个检查逻辑静默失效。










