首页 > 应用开发 > IT综合 > 请谨慎使用 @Builder 注解!

来源:阿里云开发者
阿里妹导读
本文主要指出 @Builder 存在的一些问题,指出它并不是链式编程的最佳实践。
一、前言
二、为什么?
可以看第三部分的例子, @Builder 注解产生的 Builder 类的构造方法默认并不能限定必传参数。
如果非要使用 @Builder ,那么不要用 @Data ,要用 @Getter。相对来说,反而 @Accessors 的行为更符合这个要求。
实际使用中经常发现 @Builder 滥用的情况,有些仅仅一两个属性的类也都要用 @Builder,真的没必要用,直接用全参的构造方法都比这更简洁。
三、怎么做?
3.1 不使用 @Builder
package io.gitrebase.demo;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;public class APIResponse<T> {private T payload;private Status status;}
使用示例:
package io.gitrebase.demo;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestControllerAdvice;public class ApplicationExceptionHandler {public APIResponse handleException(Exception exception) {log.error("Unhandled Exception", exception);Status status = new Status();status.setResponseCode("RESPONSE_CODE_IDENTIFIER");status.setDescription("Bla Bla Bla");APIResponse response = new APIResponse();response.setStatus(status);return response;}}
3.2 使用 @Builder
package io.gitrebase.demo;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;public class APIResponse<T> {private T payload;private Status status;}
等价代码:
package io.gitrebase.demo;import lombok.AccessLevel;import lombok.AllArgsConstructor;import lombok.Getter;// Make constructor private so object cannot be created outside the class// Allowing only PersonBuilder.build to create an instance of the class(access = AccessLevel.PRIVATE)public class Person {private String firstName;private String lastName;private String email;private String mobile;// PersonBuilder can only be created with firstName, lastName// which implies that Person object ll always be instantiated with firstName & lastNamepublic static PersonBuilder builder(String firstName, String lastName) {return new PersonBuilder(firstName, lastName);}public static class PersonBuilder {private final String firstName;private final String lastName;private String email;private String mobile;// Make constructor private so object cannot be created outside the class// Allowing only Person.builder to create an instance of PersonBuilderprivate PersonBuilder(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;}public PersonBuilder email(String email) {this.email = email;return this;}public PersonBuilder mobile(String mobile) {this.mobile = mobile;return this;}public Person build() {return new Person(firstName, lastName, email, mobile);}}}
使用示例:
package io.gitrebase.demo;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestControllerAdvice;@Slf4j@RestControllerAdvice(basePackageClasses = io.gitrebase.demo.RestApplication.class)public class ApplicationExceptionHandler {@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR)public APIResponse handleException(Exception exception) {log.error("Unhandled Exception", exception);return APIResponse.builder().status(Status.builder().responseCode("RESPONSE_CODE_IDENTIFIER").description("Bla Bla Bla").build()).build();}}
3.3 使用 @Accessors 注解
package io.gitrebase.demo;import lombok.Data;import lombok.experimental.Accessors;(chain = true)public class APIResponse<T> {private T payload;private Status status;}
等价代码:
package io.gitrebase.demo;import lombok.experimental.Accessors;public class APIResponse<T> {private T payload;private Status status;public T getPayload() {return this.payload;}public APIResponse<T> setPayload(T payload) {this.payload = payload;return this;}public Status getStatus() {return this.status;}public APIResponse<T> setStatus(Status status) {this.status = status;return this;}}
使用示例:
package io.gitrebase.demo;import lombok.extern.slf4j.Slf4j;import lombok.var;import org.springframework.http.HttpStatus;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestControllerAdvice;public class ApplicationExceptionHandler {public APIResponse handleException(Exception exception) {log.error("Unhandled Exception", exception);var status = new Status().setResponseCode("RESPONSE_CODE_IDENTIFIER").setDescription("Bla Bla Bla");return new APIResponse().setStatus(status);}}
原文中给的示例相对简单,实际运用时,如果属性较多,且分为必传属性和选填属性时,可以将必传参数定义在构造方法中,加上 @Accessors 注解,这样就可以实现必传参数的传入,又可以实现选填参数的链式调用。
import lombok.Accessors;// 使用 @Accessors 注解,设置 chain = true,生成返回 this 的 setter 方法(chain = true)public class Student {// 定义必传属性,使用 final 修饰,不提供 setter 方法private final int studentId; // 学生IDprivate final int grade; // 年级private final int classNum; // 班级// 定义选填属性,提供 setter 方法private String name; // 姓名private String gender; // 性别private String address; // 住址// 定义构造方法,接收必传参数public Student(int studentId, int grade, int classNum) {this.studentId = studentId;this.grade = grade;this.classNum = classNum;}// 省略 getter 和 setter 方法}// 使用示例Student student = new Student(1001, 3, 8) // 创建一个学生对象,传入必传参数.setName("张三") // 设置姓名.setGender("男") // 设置性别.setAddress("北京市朝阳区"); // 设置住址
另外该注解还有很多设置项,感兴趣可以进一步研究:
/** * A container for settings for the generation of getters, setters and "with"-ers. * <p> * Complete documentation is found at <a href="https://projectlombok.org/features/experimental/Accessors">the project lombok features page for @Accessors</a>. * <p> * Using this annotation does nothing by itself; an annotation that makes lombok generate getters, setters, or "with"-ers * such as {@link lombok.Setter} or {@link lombok.Data} is also required. */@Target({ElementType.TYPE, ElementType.FIELD})@Retention(RetentionPolicy.SOURCE)public @interface Accessors { /** * If true, accessors will be named after the field and not include a {@code get} or {@code set} * prefix. If true and {@code chain} is omitted, {@code chain} defaults to {@code true}.<br> * NB: This setting has no effect on {@code @With}; they always get a "with" prefix.<br> * <strong>default: false</strong> * * @return Whether or not to make fluent methods (named {@code fieldName()}, not for example {@code setFieldName}). */ boolean fluent() default false; /** * If true, setters return {@code this} instead of {@code void}. * <strong>default: false</strong>, unless {@code fluent=true}, then <strong>default: true</strong> * * @return Whether or not setters should return themselves (chaining) or {@code void} (no chaining). */ boolean chain() default false; /** * If true, generated accessors will be marked {@code final}. * <strong>default: false</strong> * * @return Whether or not accessors should be marked {@code final}. */ boolean makeFinal() default false; /** * If present, only fields with any of the stated prefixes are given the getter/setter treatment. * Note that a prefix only counts if the next character is NOT a lowercase character or the last * letter of the prefix is not a letter (for instance an underscore). If multiple fields * all turn into the same name when the prefix is stripped, an error will be generated. * * @return If you are in the habit of prefixing your fields (for example, you name them {@code fFieldName}, specify such prefixes here). */ String[] prefix() default {};}3.4 使用 IDEA 默认 Setter 生成功能

四、启发
参考链接:
[1]https://medium.com/gitrebase/oh-stop-using-builder-9061a5911d8c
[2]https://www.baeldung.com/lombok-builder-inheritance
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70024922/viewspace-2981859/ ,如需转载,请注明出处,否则将追究法律责任。