搜索 K
Appearance
Appearance
MutualExclusionProcessor 是本模块唯一的 AbstractProcessor,职责单一:阻止 @RawResponse 与 @MyApiResponse 出现在同一个元素上。
Java APT(Annotation Processing Tool)是 javac 内置的扩展机制。编译器在 round-by-round 的解析过程中,会从 classpath 上读取 META-INF/services/javax.annotation.processing.Processor 列出的处理器,实例化后调用它们的 process(...) 方法,让处理器有机会读取语法树元素、生成新代码或报告错误。
整个机制纯编译期,不影响运行时性能。
META-INF/services/javax.annotation.processing.Processor 文件内容只有一行:
cn.cvking.forge.processor.check.MutualExclusionProcessor业务工程在编译时只要把 processor jar 放入 classpath(通过 Maven 依赖即可),javac 就会自动发现并加载它,无需在 pom.xml 额外配置 <annotationProcessorPaths>。这种「依赖即生效」的体验是 SPI 的标准设计。
@SupportedAnnotationTypes({
"cn.cvking.forge.processor.annotation.MyApiResponse",
"cn.cvking.forge.processor.annotation.RawResponse"
})
@SupportedSourceVersion(SourceVersion.RELEASE_17)
public class MutualExclusionProcessor extends AbstractProcessor {@SupportedAnnotationTypes:声明处理器关心的注解,javac 只会在源码出现这两个注解时才回调 process,避免无效轮询@SupportedSourceVersion:声明支持的 Java 版本。如果使用更高版本编译会得到一条警告,但处理器仍会运行。框架要求 JDK 17+,这里直接写死 RELEASE_17@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
for (Element element : roundEnv.getElementsAnnotatedWith(MyApiResponse.class)) {
if (element.getAnnotation(RawResponse.class) != null) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"Semantic Error: @MyApiResponse and @RawResponse cannot be used on the same element.",
element
);
}
}
for (Element element : roundEnv.getElementsAnnotatedWith(RawResponse.class)) {
if (element.getAnnotation(MyApiResponse.class) != null) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"Semantic Error: @RawResponse and @MyApiResponse cannot be used on the same element.",
element
);
}
}
return true;
}实现非常直白:
单向扫描在大多数情况下足够,但 APT 是分轮次(round)的:第一个 round 可能只解析到带 @MyApiResponse 的源文件,第二个 round 才解析到带 @RawResponse 的源文件。双向扫描可以确保只要任意一轮里两边都出现在同一个 Element 上,就能被捕获。
实际上 Element.getAnnotation(...) 检查的是当前 Element 的元数据,与 round 无直接关系,所以单向已经足够。这里写双向更多是「防御式编程」,代价低、保险高。
messager.printMessage(kind, msg, element) 的第三个参数 element 决定了错误在源码中的高亮位置。对于方法上的注解,element 是 ExecutableElement,javac 会把整个方法名高亮;对于类上的注解,element 是 TypeElement,会高亮类名行。
IDE 通常订阅 javac 的 Diagnostic 事件,所以这条错误会直接在编辑器里飘红,不必等到完整编译失败才被发现。
本处理器只走「校验」路径,没有调用 processingEnv.getFiler() 生成任何文件。process 方法返回 true 表示「已经处理过这些注解,后续处理器不必再处理」,对当前用法实际效果与 false 没有差异,因为这两个注解只属于本处理器。
只需在 process 方法里追加一组扫描。例如,假设要求 @APIVersion 不能与 @ExceptionHandler 同时出现:
for (Element element : roundEnv.getElementsAnnotatedWith(APIVersion.class)) {
if (element.getAnnotation(ExceptionHandler.class) != null) {
messager.printMessage(
Diagnostic.Kind.ERROR,
"Semantic Error: @APIVersion cannot be used on @ExceptionHandler methods.",
element
);
}
}记得把 @APIVersion 与 @ExceptionHandler 加进 @SupportedAnnotationTypes,否则 javac 不会回调本处理器。
例如根据注解生成 OpenAPI 描述文件、生成路由清单 JSON,可以引入 processingEnv.getFiler().createSourceFile(...),但此时建议拆出独立的 Processor 类,避免单类承担过多职责。
Lombok 走的是「修改 AST」路径,使用了 javac 的内部 API(com.sun.tools.javac),对版本兼容性敏感。本模块只做「读取 + 报错」,是 APT 的标准用法,不需要任何第三方依赖。