行业资讯
让注解在编译时“写代码”——自动生成 Builder
一、又遇到了新麻烦订单对象需要经常被创建但它的构造方法参数很多new Order(O001, 手机, new BigDecimal(5999), 1, new Date(), 内部备注); // 谁能记得住参数顺序极易写错。我们想用建造者模式Builder但为每个类手写 Builder 又很枯燥。能不能在订单类上贴一个 Builder 注解编译时自动生成 Builder 代码这就是 Lombok 这种神器的原理我们现在自己实现一个极简版。二、编译时注解处理是什么上一节我们的 ExcelColumn 保留到运行时通过反射读取会有轻微的性能损耗。这次我们要用的注解生命周期在编译时SOURCE它不进入 .class 文件而是由注解处理器在编译阶段读到然后生成新的 .java 源文件之后再一起编译成 .class。整个流程源码 注解 - 注解处理器生成新源码 - javac 编译所有源码 - class文件三、第一步定义 Builder 注解这次保留策略用 SOURCE因为运行时不需要它。import java.lang.annotation.*; Target(ElementType.TYPE) // 贴在类上 Retention(RetentionPolicy.SOURCE) // 编译时就被丢弃 public interface Builder { }四、第二步实现注解处理器核心处理器需要在 javac 编译时被调用。我们创建一个处理器类并让它能识别 Builder。import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.PrintWriter; import java.util.*; // 告诉javac这个处理器能处理哪些注解 SupportedAnnotationTypes(Builder) SupportedSourceVersion(SourceVersion.RELEASE_8) public class BuilderProcessor extends AbstractProcessor { Override public boolean process(Set? extends TypeElement annotations, RoundEnvironment roundEnv) { // 遍历每一个被Builder标注的元素类 for (Element element : roundEnv.getElementsAnnotatedWith(Builder.class)) { if (element.getKind() ! ElementKind.CLASS) { continue; // 只处理类 } TypeElement classElement (TypeElement) element; String packageName processingEnv.getElementUtils().getPackageOf(classElement).getQualifiedName().toString(); String className classElement.getSimpleName().toString(); String builderClassName className Builder; // 收集类中所有字段包括私有的 getter 或直接访问字段这里简化我们约定该类有标准的getter方法或者我们直接使用字段名生成setter风格的Builder方法 ListVariableElement fields new ArrayList(); for (Element enclosed : classElement.getEnclosedElements()) { if (enclosed.getKind() ElementKind.FIELD) { fields.add((VariableElement) enclosed); } } // 生成Builder类的源代码 StringBuilder builderCode new StringBuilder(); builderCode.append(package ).append(packageName).append(;\n\n); builderCode.append(public class ).append(builderClassName).append( {\n); // 字段和对应的set方法 for (VariableElement field : fields) { String fieldName field.getSimpleName().toString(); String fieldType field.asType().toString(); builderCode.append( private ).append(fieldType).append( ).append(fieldName).append(;\n); builderCode.append( public ).append(builderClassName).append( ).append(fieldName).append(().append(fieldType).append( ).append(fieldName).append() {\n); builderCode.append( this.).append(fieldName).append( ).append(fieldName).append(;\n); builderCode.append( return this;\n); builderCode.append( }\n); } // build方法 builderCode.append( public ).append(className).append( build() {\n); builderCode.append( ).append(className).append( obj new ).append(className).append(();\n); for (VariableElement field : fields) { String fieldName field.getSimpleName().toString(); // 为了简单我们假设目标类有对应的set方法如setOrderId String setter set Character.toUpperCase(fieldName.charAt(0)) fieldName.substring(1); builderCode.append( obj.).append(setter).append((this.).append(fieldName).append();\n); } builderCode.append( return obj;\n); builderCode.append( }\n); builderCode.append(}\n); // 写出生成的源文件 try { JavaFileObject sourceFile processingEnv.getFiler().createSourceFile(packageName . builderClassName); try (PrintWriter out new PrintWriter(sourceFile.openWriter())) { out.println(builderCode.toString()); } } catch (IOException e) { e.printStackTrace(); } } return true; } }这个处理器做的事扫描到标记 Builder 的类。提取该类的所有字段。生成一个名为 原类名Builder 的类每个字段有一个同名方法返回Builder自身最后 build() 方法里调用原类的 setter 来构造对象。五、第三步配置处理器让它生效用 Maven 编译的话需要在 pom.xml 中配置注解处理器路径或者在 META-INF/services 里注册。这里用最简单的手动方式演示直接在代码结构里放好处理器然后在编译时指定 -processor 参数或在 IDE 中开启注解处理。假设我们项目结构src/main/java/ com/example/ Order.java (加 Builder 注解) BuilderProcessor.java编译时如果我们让 javac 运行处理器就会自动在 build/generated-sources/ 之类的目录下生成 OrderBuilder.java。四、第四步使用时发生了什么我们给 Order 类加上 BuilderBuilder public class Order { private String orderId; private String productName; // ... 字段、setter和getter }编译后自动生成的 OrderBuilder.java 大概是这个样子package com.example; public class OrderBuilder { private String orderId; private String productName; // 其他字段... public OrderBuilder orderId(String orderId) { this.orderId orderId; return this; } public OrderBuilder productName(String productName) { this.productName productName; return this; } // 其他字段的类似方法... public Order build() { Order obj new Order(); obj.setOrderId(this.orderId); obj.setProductName(this.productName); // ... return obj; } }程序员就可以直接这么写Order order new OrderBuilder() .orderId(O123) .productName(机械键盘) .price(new BigDecimal(299)) .quantity(2) .build();你感受一下这个变化不用再记构造方法参数顺序。代码像写自然语言一样流畅。OrderBuilder 完全由注解在编译时自动生成你零手写成本。注解真正的威力在于它把原本需要写死在代码里的“元数据”和“逻辑模板”提取出来让框架或编译器帮我们自动生成代码或自动执行逻辑。无论是运行时读注解的反射框架还是编译时代码生成器本质上都是程序读程序让开发效率成倍提升。
郑州网站建设
网页设计
企业官网