- 之前的博客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);
}
- 执行结果如下:
3. 后话
- 有了之前的铺垫,这次实现@Builder的编译时注解处理器就顺畅多了
- 除了在使用JavaPoet生成Java代码时,因为使用不熟练犯了一些错误,其他都很顺利
- 下一个目标,希望通过直接修改源类,实现Builder模式