Java编译时注解
编译时注解(APT)在编译期处理注解,生成代码,避免运行时反射开销。
编译时 vs 运行时注解
| 特性 | 编译时注解 | 运行时注解 |
|---|---|---|
| 处理时机 | 编译期 | 运行期 |
| 保留策略 | SOURCE 或 CLASS | RUNTIME |
| 性能 | 无运行时开销 | 反射有性能损耗 |
| 典型框架 | Lombok、ButterKnife | Spring、MyBatis |
APT工作流程
Java
源码(.java) → 编译器解析注解 → 调用处理器 → 生成新文件 → 编译新文件 → .class
实战:生成Builder模式代码
1. 定义注解
Java
@Retention(RetentionPolicy.SOURCE) // 只在源码保留
@Target(ElementType.TYPE)
public @interface GenerateBuilder {
String prefix() default "with";
}
2. 注解处理器
Java
@SupportedAnnotationTypes("com.example.GenerateBuilder")
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (TypeElement annotation : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
if (element.getKind() == ElementKind.CLASS) {
generateBuilder((TypeElement) element);
}
}
}
return true;
}
private void generateBuilder(TypeElement classElement) {
String className = classElement.getSimpleName().toString();
String packageName = getPackageName(classElement);
String builderName = className + "Builder";
// 收集字段
List<VariableElement> fields = new ArrayList<>();
for (Element member : classElement.getEnclosedElements()) {
if (member.getKind() == ElementKind.FIELD) {
fields.add((VariableElement) member);
}
}
// 生成Builder代码
try {
JavaFileObject file = processingEnv.getFiler()
.createSourceFile(packageName + "." + builderName);
try (PrintWriter out = new PrintWriter(file.openWriter())) {
writeBuilder(out, packageName, className, builderName, fields);
}
} catch (IOException e) {
processingEnv.getMessager()
.printMessage(Diagnostic.Kind.ERROR, e.getMessage());
}
}
private void writeBuilder(PrintWriter out, String packageName,
String className, String builderName,
List<VariableElement> fields) {
out.println("package " + packageName + ";");
out.println();
out.println("public class " + builderName + " {");
// 字段
for (VariableElement field : fields) {
out.println(" private " + field.asType() + " " + field.getSimpleName() + ";");
}
out.println();
// setter方法
for (VariableElement field : fields) {
String name = field.getSimpleName().toString();
String type = field.asType().toString();
String methodName = "with" + capitalize(name);
out.println(" public " + builderName + " " + methodName + "(" + type + " " + name + ") {");
out.println(" this." + name + " = " + name + ";");
out.println(" return this;");
out.println(" }");
out.println();
}
// build方法
out.println(" public " + className + " build() {");
out.println(" " + className + " obj = new " + className + "();");
for (VariableElement field : fields) {
String name = field.getSimpleName().toString();
out.println(" obj." + name + " = this." + name + ";");
}
out.println(" return obj;");
out.println(" }");
out.println("}");
}
private String capitalize(String str) {
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
private String getPackageName(TypeElement element) {
Element enclosing = element.getEnclosingElement();
while (enclosing.getKind() != ElementKind.PACKAGE) {
enclosing = enclosing.getEnclosingElement();
}
return ((PackageElement) enclosing).getQualifiedName().toString();
}
}
3. 使用生成的Builder
Java
@GenerateBuilder
public class User {
private String name;
private int age;
private String email;
}
// 编译后自动生成UserBuilder,可直接使用
User user = new UserBuilder()
.withName("张三")
.withAge(25)
.withEmail("zhang@example.com")
.build();
JavaPoet简化代码生成
JavaPoet是Square开源的代码生成库:
text
private void generateBuilder(TypeElement classElement) {
String className = classElement.getSimpleName().toString();
String packageName = getPackageName(classElement);
// 定义Builder类
TypeSpec.Builder builder = TypeSpec.classBuilder(className + "Builder")
.addModifiers(Modifier.PUBLIC);
// 添加字段和方法
for (Element member : classElement.getEnclosedElements()) {
if (member.getKind() == ElementKind.FIELD) {
VariableElement field = (VariableElement) member;
// 字段
builder.addField(TypeName.get(field.asType()),
field.getSimpleName().toString(), Modifier.PRIVATE);
// setter方法
MethodSpec setter = MethodSpec.methodBuilder(
"with" + capitalize(field.getSimpleName().toString()))
.addModifiers(Modifier.PUBLIC)
.returns(ClassName.get(packageName, className + "Builder"))
.addParameter(TypeName.get(field.asType()),
field.getSimpleName().toString())
.addStatement("this.$N = $N", field.getSimpleName(), field.getSimpleName())
.addStatement("return this")
.build();
builder.addMethod(setter);
}
}
// 写入文件
JavaFile javaFile = JavaFile.builder(packageName, builder.build()).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
// 处理异常
}
}
Lombok原理
Lombok通过修改AST(抽象语法树)实现代码增强:
text
源码 → 解析AST → 注解处理器 → 修改AST → 生成.class
关键区别:标准APT只能生成新文件,Lombok通过hack方式修改原有AST。
编译时注解优势
- 性能优势:无运行时反射开销
- 类型安全:编译期检查,错误提前发现
- IDE支持:生成的代码可被IDE识别
- 调试友好:生成的代码可直接查看和调试
注意事项
处理器必须注册到 META-INF/services/javax.annotation.processing.Processor
多轮处理时注意避免重复生成文件
使用 Filer 创建文件,不要用 File API
roundEnv.processingOver() 判断是否最后一轮处理
要点总结
- 编译时注解在编译期处理,保留策略为 SOURCE 或 CLASS
- 继承 AbstractProcessor 实现 process() 方法
- 使用 Filer 生成Java文件,JavaPoet简化代码生成
- 相比运行时注解,性能更优、类型更安全
- Lombok通过修改AST实现字段注入,比标准APT更强大
📝 发现内容有误?点击此处直接编辑