如何创建可自行生成注解并加以使用的Java注解处理器?
咱们这个需求核心是要让注解处理器干两件事:先根据标记生成枚举专属注解,再能识别并处理自己刚生成的注解,而且得搞定编译阶段的轮次问题,不然编译器根本不认你刚生成的代码。下面我一步步给你拆解怎么实现:
一、先搭好触发生成的“入口”注解
首先得定义一个用来触发注解生成的初始注解,比如@GenerateAnnotation,专门标记在枚举上,告诉处理器“给这个枚举生成专属注解”。代码这么写:
@Retention(RetentionPolicy.SOURCE) // 只在编译阶段生效,不用保留到运行时 @Target(ElementType.TYPE) // 能标记在类、枚举这些类型上 public @interface GenerateAnnotation { }
二、实现第一轮处理:生成枚举专属注解
接下来写注解处理器的核心逻辑,先处理@GenerateAnnotation标记的枚举,生成对应的专属注解。
1. 处理器基础骨架
先写个继承AbstractProcessor的处理器类,加上必要的注解让编译器识别它:
@SupportedAnnotationTypes("com.yourpackage.GenerateAnnotation") // 指定要处理的初始注解 @SupportedSourceVersion(SourceVersion.RELEASE_8) // 适配你用的Java版本 public class EnumAnnotationProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 核心处理逻辑写在这 return false; // 重点!返回false表示还要继续处理后续轮次,不能返回true } }
这里一定要返回false,因为我们生成新代码后,需要编译器触发下一轮处理,返回true的话编译就直接结束了,新生成的注解没人管。
2. 扫描枚举并生成专属注解
在process方法里,扫描所有被@GenerateAnnotation标记的枚举,然后生成对应的注解。推荐用JavaPoet来生成代码,比手动拼字符串靠谱多了,能避免很多语法错误。
举个JavaPoet的例子,生成MyEnum对应的MyEnumAnnotation:
// 遍历所有被@GenerateAnnotation标记的元素 for (Element element : roundEnv.getElementsAnnotatedWith(GenerateAnnotation.class)) { // 先判断是不是枚举,不是的话抛编译错误 if (element.getKind() != ElementKind.ENUM) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@GenerateAnnotation只能标记在枚举类上!", element); continue; } TypeElement enumElement = (TypeElement) element; String enumName = enumElement.getSimpleName().toString(); String annotationName = enumName + "Annotation"; String packageName = processingEnv.getElementUtils().getPackageOf(enumElement).getQualifiedName().toString(); // 用JavaPoet构建注解类 TypeSpec annotationSpec = TypeSpec.annotationBuilder(annotationName) .addModifiers(Modifier.PUBLIC) // 给生成的注解加元注解,比如保留到编译阶段 .addAnnotation(AnnotationSpec.builder(Retention.class) .addMember("value", "$T.SOURCE", RetentionPolicy.class) .build()) // 定义注解的value属性,类型就是当前枚举 .addMethod(MethodSpec.methodBuilder("value") .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) .returns(ClassName.get(enumElement)) .build()) .build(); // 生成Java文件,写到编译系统的输出目录 JavaFile javaFile = JavaFile.builder(packageName, annotationSpec).build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "生成注解失败:" + e.getMessage(), element); } }
这段代码干的事就是:找到标记的枚举,生成一个以枚举名+Annotation命名的注解,注解的value类型就是这个枚举本身,还自动加了元注解。
三、实现第二轮处理:识别并使用自己生成的注解
现在处理器已经能生成注解了,接下来要让它能处理这些生成的注解,比如用户在其他类上用了@MyEnumAnnotation(MyEnum.TEST),我们要能扫描到并生成业务代码。
1. 让处理器支持多轮扫描
首先,把处理器的@SupportedAnnotationTypes改成通配符*,这样它能扫描到所有注解(包括新生成的),或者你也可以在process方法里动态判断注解的命名规则(比如以Annotation结尾的)。
然后,在process方法里,除了处理@GenerateAnnotation,还要加一段逻辑扫描所有自定义的枚举专属注解:
// 处理第一轮生成的枚举专属注解 for (TypeElement annotationType : roundEnv.getRootElements().stream() .filter(e -> e.getKind() == ElementKind.ANNOTATION_TYPE) .map(e -> (TypeElement) e) .filter(e -> e.getSimpleName().toString().endsWith("Annotation")) .toList()) { // 扫描所有使用这个注解的元素 Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(annotationType); for (Element elem : annotatedElements) { // 这里写你要做的逻辑,比如生成重复代码 // 举个例子,生成一个toString方法或者其他业务代码 generateBoilerplateCode(elem, annotationType); } }
这样,当第一轮生成注解后,编译器会触发第二轮处理,这时候处理器就能扫描到新生成的注解,以及使用它的代码,然后做后续处理。
2. 注意编译轮次的细节
- 每一轮处理后,编译器会检查有没有新生成的源文件,如果有就触发下一轮,直到没有新文件为止。所以你的处理器要能正确处理多轮,不要在第一轮就把所有逻辑都做完。
- 生成的注解如果需要在运行时使用,记得把元注解的Retention改成
RUNTIME,但如果只是编译阶段用,SOURCE就够了,更高效。
四、打包和使用处理器
最后要把处理器打包成jar,并且在jar里加上META-INF/services/javax.annotation.processing.Processor文件,文件内容是你的处理器的全类名,比如com.yourpackage.EnumAnnotationProcessor,这样编译器(比如javac、IDEA的编译器)才能自动发现这个处理器。
使用的时候,只要把这个jar加到项目依赖里,然后:
- 定义枚举并标记
@GenerateAnnotation; - 在其他类上用生成的
@MyEnumAnnotation; - 编译项目,处理器会自动完成两轮处理,生成你需要的代码。
备注:内容来源于stack exchange,提问作者cancech

