如何通过注解等技术在编译时为Java类添加方法?类Lombok定制化实现
编译时基于自定义注解生成Java方法的替代方案
如果你想避开Lombok,自己实现编译时基于注解动态添加方法的逻辑,下面是几种可行的技术方案和代码示例:
一、Java原生注解处理器(Annotation Processing API)
这是最基础的原生方案,不需要任何第三方依赖,完全依托JDK自带的注解处理机制实现。
步骤1:定义自定义注解
首先创建一个仅在源码阶段生效的注解,用于标记需要生成getter的字段:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface MyGetter { }
步骤2:实现注解处理器
编写继承自AbstractProcessor的处理器类,扫描标注了@MyGetter的字段,生成对应的getter代码:
import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.PrintWriter; import java.util.Set; @SupportedAnnotationTypes("com.example.MyGetter") @SupportedSourceVersion(SourceVersion.RELEASE_17) public class MyGetterProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 遍历所有被@MyGetter标注的元素 for (Element element : roundEnv.getElementsAnnotatedWith(MyGetter.class)) { if (!(element instanceof VariableElement field)) continue; TypeElement enclosingClass = (TypeElement) field.getEnclosingElement(); String className = enclosingClass.getSimpleName().toString(); String packageName = processingEnv.getElementUtils().getPackageOf(enclosingClass).toString(); String fieldName = field.getSimpleName().toString(); String fieldType = field.asType().toString(); // 生成包含getter的辅助类 try { JavaFileObject file = processingEnv.getFiler().createSourceFile(packageName + "." + className + "Accessor"); try (PrintWriter writer = new PrintWriter(file.openWriter())) { writer.println("package " + packageName + ";"); writer.println(); writer.println("public class " + className + "Accessor {"); writer.println(" private final " + className + " target;"); writer.println(); writer.println(" public " + className + "Accessor(" + className + " target) {"); writer.println(" this.target = target;"); writer.println(" }"); writer.println(); // 生成标准getter方法 String getterName = "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); writer.println(" public " + fieldType + " " + getterName + "() {"); writer.println(" return target." + fieldName + ";"); writer.println(" }"); writer.println("}"); } } catch (IOException e) { processingEnv.getMessager().printError("生成getter失败:" + e.getMessage()); } } return true; } }
步骤3:注册处理器
在项目的src/main/resources/META-INF/services目录下创建javax.annotation.processing.Processor文件,写入处理器的全类名:
com.example.MyGetterProcessor
测试
编写测试类并编译,处理器会自动生成UserAccessor.java:
package com.example; public class User { @MyGetter private String username; private int age; public User(String username, int age) { this.username = username; this.age = age; } }
二、Google Auto系列工具
Google Auto提供了简化注解处理器开发的工具,比如AutoService可以自动生成处理器注册文件,避免手动编写services文件。
添加依赖(Maven)
<dependency> <groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> <version>1.1.1</version> <scope>provided</scope> </dependency>
修改处理器
只需给处理器类添加@AutoService(Processor.class)注解,就不需要手动创建services文件了:
import com.google.auto.service.AutoService; import javax.annotation.processing.*; // 其他导入省略 @AutoService(Processor.class) @SupportedAnnotationTypes("com.example.MyGetter") @SupportedSourceVersion(SourceVersion.RELEASE_17) public class MyGetterProcessor extends AbstractProcessor { // 实现逻辑和原生方案一致 }
三、Byte Buddy编译时处理
Byte Buddy通常用于运行时代码生成,但也支持编译时通过注解处理器直接修改类的字节码,比原生API更简洁,适合需要直接修改原类的场景。
添加依赖(Maven)
<dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.14.11</version> </dependency> <dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy-annotation-processing</artifactId> <version>1.14.11</version> <scope>provided</scope> </dependency>
实现处理器
// 自定义注解 @Target(ElementType.FIELD) @Retention(RetentionPolicy.SOURCE) public @interface MyByteBuddyGetter { } // Byte Buddy处理器 import net.bytebuddy.build.Plugin; import net.bytebuddy.build.Plugin.Factory.UsingReflection; import net.bytebuddy.description.field.FieldDescription; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.implementation.FieldAccessor; import net.bytebuddy.matcher.ElementMatchers; @UsingReflection public class MyByteBuddyGetterProcessor implements Plugin { @Override public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription, Plugin.Context context) { // 给所有标注了@MyByteBuddyGetter的字段添加getter方法 for (FieldDescription field : typeDescription.getDeclaredFields().filter(ElementMatchers.isAnnotatedWith(MyByteBuddyGetter.class))) { String getterName = "get" + Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1); builder = builder.defineMethod(getterName, field.getType(), javax.lang.model.element.Modifier.PUBLIC) .intercept(FieldAccessor.ofField(field.getName())); } return builder; } @Override public void close() throws Exception {} }
注册处理器后,编译标注了@MyByteBuddyGetter的类,Byte Buddy会直接在原类中注入getter方法,不需要生成辅助类。
最佳实践
- 优先选用原生注解处理器:无依赖、兼容性强,适合简单的定制需求。
- 复杂场景用Google Auto或Byte Buddy:减少样板代码,提供更丰富的代码生成API。
- 注解保留策略用
SOURCE:不需要在运行时保留,降低内存开销。 - 处理过程中用
Messager输出日志/错误:方便调试处理器的执行逻辑。 - 测试时可通过
javac -processor 全类名指定处理器,或在IDE中开启注解处理器支持。
内容的提问来源于stack exchange,提问作者saba artkmeladze
相关产品推荐
相关产品推荐

