• 之前的博客3. 自定义Java编译时注解处理器,介绍了:① 编译时注解处理器的一些基础知识,② 如何实现一个简单的编译时注解处理器,③ 在学习过程中遇到的问题和一些解决办法
  • 这次将通过实现@Builder注解,来加深对上一节知识的理解

1. 絮絮叨叨

  • 关于Builder模式,实现上总是大同小于:
  • Builder类的定义:① 将Builder类定义为目标类的静态内部类;② 将Builder类定义为一个外部类
  • Builder实例的创建:① 通过new直接创建; ② 通过静态的builder()方法创建
  • 目标对象的创建:① Builder实例作为目标类构造函数的入参;② Builder实例收集到的属性值,直接作为目标类构造函数的入参
  • 为了方便,本文将Builder类定义为外部类,同时通过目标类的setter方法,实现目标对象的创建
// 目标类
public class Student {
    private String name;
    private int age;
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Student{" + "name='" + name + '\'' + ", age=" + age + '}';
    }
}
// Builder类
public class StudentBuilder {
    private String name;
    private int age;
    public StudentBuilder setName(String name) {
        this.name = name;
        return this;
    }
    public StudentBuilder setAge(int age) {
        this.age = age;
        return this;
    }
    public Student build() {
        Student student = new Student();
        student.setAge(age);
        student.setName(name);
        return student;
    }
}

// main方法中使用Builder模式创建Student对象
public static void main(String[] args) {
    Student student = new StudentBuilder()
    		.setAge(24)
            .setName("lucy")
            .build();
    System.out.println(student);
}

2. 实现@Builder注解

  • @Builder注解,作用于实体类上
  • 对应的编译时注解处理器为BuilderProcessor,可以为实体类自动生成对应的Builder类

2.1 定义@Builder注解

  • 同样地,在annotation-processor模块的sunrise.annotation包下,创建@Builder注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface Builder {
}

2.2 实现BuilderProcessor

  • 主要分为以下几个步骤:
  • @Builder作用于public class,先检查被标注类是否为public class
  • 获取类的字段信息,用于构建FieldSpec和setter方法的MethodSpec;对于字段,其访问权限和原字段一致;对于方法,方法名采用小驼峰命名
  • 生成build()方法的MethodSpec,用于新建源类的实例
  • 组装FieldSpec和MethodSpec,构建TypeSpec
  • 最后,通过JavaFile生成对应的Java文件
  • 代码如下
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("sunrise.annotation.Builder")
public class BuilderProcessor extends AbstractProcessor {
    private Messager messager;
    private Elements elementUtils;
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.messager = processingEnv.getMessager();
        this.filer = processingEnv.getFiler();
        this.elementUtils = processingEnv.getElementUtils();
    }


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 遍历注解处理器中注册的注解
        for (TypeElement annotation : annotations) {
            String annotationName = annotation.getSimpleName().toString();
            // 遍历被注解标注的元素(类)
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
            for (Element element : elements) {
                // 要求必须为public class
                if (element.getKind() != ElementKind.CLASS || !element.getModifiers().contains(Modifier.PUBLIC)) {
                    messager.printMessage(Diagnostic.Kind.ERROR, "@" + annotationName + "must be used for public class");
                }

                // 创建TypeSpec
                PackageElement packageElement = elementUtils.getPackageOf(element);
                String packageName = packageElement.getQualifiedName().toString();
                TypeSpec typeSpec = generateBuilderClass(element, packageName);

                // 生成对应的Java文件
                JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
                try {
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate Java file for class ", element);
                }
            }
        }

        return roundEnv.processingOver();
    }

    public TypeSpec generateBuilderClass(Element element, String packageName) {
        // 获取类名,为构建Builder类做准备
        TypeElement classElement = (TypeElement) element;
        String sourceName = classElement.getSimpleName().toString();
        // 创建ClassName
        ClassName sourceClass = ClassName.get(packageName, sourceName);
        ClassName builderClass = ClassName.get(packageName, sourceName + "Builder");

        // 获取所有的非final字段
        Set<Element> fields = new HashSet<>();

        for (Element member : classElement.getEnclosedElements()) {
            if (member.getKind() == ElementKind.FIELD && !member.getModifiers().contains(Modifier.FINAL)) {
                fields.add(member);
            }
        }

        // 生成FieldSpec和MethodSpec
        List<FieldSpec> fieldSpecs = new ArrayList<>();
        List<MethodSpec> methodSpecs = new ArrayList<>();
        for (Element field : fields) {
            // 获取字段的名称、类型和权限修饰符
            String fieldName = field.getSimpleName().toString();
            TypeName typeName = TypeName.get(field.asType());
            List<Modifier> modifiers = field.getModifiers().stream().filter(x -> x == Modifier.PUBLIC || x == Modifier.PROTECTED || x == Modifier.PRIVATE).collect(Collectors.toList());

            // 创建字段,如果为包访问权限,筛选出来的modifiers为空
            FieldSpec fieldSpec;
            if (modifiers.size() > 0) {
                fieldSpec = FieldSpec.builder(typeName, fieldName, modifiers.get(0)).build();
            } else {
                fieldSpec = FieldSpec.builder(typeName, fieldName).build();
            }

            fieldSpecs.add(fieldSpec);
            // 创建方法
            String methodName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "set_" + fieldName);
            MethodSpec methodSpec = MethodSpec.methodBuilder(methodName)
                    .addModifiers(Modifier.PUBLIC)
                    .returns(builderClass)
                    .addParameter(typeName, fieldName)
                    .addStatement("this.$1N = $1N", fieldName)
                    .addStatement("return this")
                    .build();
            methodSpecs.add(methodSpec);
        }

        // 生成build方法
        MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("build")
                .addModifiers(Modifier.PUBLIC)
                .returns(sourceClass)
                .addStatement("$1T obj = new $1T()", sourceClass);
        for (Element field : fields) {
            String fieldName = field.getSimpleName().toString();
            String methodName = CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, "set_" + fieldName);
            methodSpecBuilder.addStatement("obj.$1N($2N)", methodName, fieldName);
        }

        MethodSpec buildMethod = methodSpecBuilder.addStatement("return obj", sourceClass).build();
        methodSpecs.add(buildMethod);

        // 构建TypeSpec
        TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(builderClass).addModifiers(Modifier.PUBLIC);
        for (FieldSpec fieldSpec : fieldSpecs) {
            typeSpecBuilder.addField(fieldSpec);
        }
        for (MethodSpec methodSpec : methodSpecs) {
            typeSpecBuilder.addMethod(methodSpec);
        }
        return typeSpecBuilder.build();
    }
}

2.3 @Builder的使用

  • Student类的定义如下,使用@Builder,省略了idea辅助生成的toString()方法
@Builder
public class Student {
    private String name;
    int age;
    protected  String address;
    public String sex;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}
  • 编译后,自动生成的Builder类如下:
package sunrise.annotation.use.entity;

import java.lang.String;

public class StudentBuilder {
  int age;

  public String sex;

  protected String address;

  private String name;

  public StudentBuilder setAge(int age) {
    this.age = age;
    return this;
  }

  public StudentBuilder setSex(String sex) {
    this.sex = sex;
    return this;
  }

  public StudentBuilder setAddress(String address) {
    this.address = address;
    return this;
  }

  public StudentBuilder setName(String name) {
    this.name = name;
    return this;
  }

  public Student build() {
    Student obj = new Student();
    obj.setAge(age);
    obj.setSex(sex);
    obj.setAddress(address);
    obj.setName(name);
    return obj;
  }
}
  • 在main方法中,通过StudentBuilder类创建Student对象
public static void main(String[] args) {
    Student student = new StudentBuilder()
            .setName("张三")
            .setSex("男")
            .setAddress("四川成都")
            .build();
    System.out.println(student);
}
  • 执行结果如下:
  • java 实体类 set get Java 实体类@builder_Java

3. 后话

  • 有了之前的铺垫,这次实现@Builder的编译时注解处理器就顺畅多了
  • 除了在使用JavaPoet生成Java代码时,因为使用不熟练犯了一些错误,其他都很顺利
  • 下一个目标,希望通过直接修改源类,实现Builder模式