Lombok Builder 构建嵌套 List 成员的最佳实践
2025-03-11 23:36:13
Lombok 生成嵌套 List 成员的 Builder
遇到了一个 Lombok Builder 的问题,我想为一个 Spring JDBC 的 SimpleJdbcCall
生成配置信息,但是遇到了一些障碍。
我有一个用 Lombok 生成的类:
@Data
@Builder
public class DatabaseCallInformation {
private String dbPackage;
private String dbProcedure;
private List<InParam> inParams;
private List<OutParam> outParams;
@Data
@Builder
public static class InParam {
private String paramName;
private int sqlType;
private String value;
}
@Data
@Builder
public static class OutParam {
private String paramName;
private int sqlType;
private SqlReturnType sqlReturnType;
private RowMapper<?> rowMapper;
}
}
我希望能够这样调用 builder:
DatabaseCallInformation result = DatabaseCallInformation.builder()
.dbPackage("MY_PACKAGE")
.dbProcedure("MY_PROCEDURE")
.inparam()
.paramName("ID")
.sqlType(Types.NUMBER)
.and()
// 可能还有更多 inparams
.outParam()
.paramName("result")
.sqlType(Types.VARCHAR)
.and()
// 可能还有更多 outParams
.build()
我需要帮助来实现 inParam()
、outParam()
和 and()
方法(不一定是这几个方法名,能实现类似功能即可)。 主要目标是能流畅地构建 DatabaseCallInformation
对象,并能方便地添加多个 InParam
和 OutParam
对象。
问题根源
Lombok 的 @Builder
注解默认不会为 List 类型的成员生成添加单个元素的 builder 方法。 它只会生成设置整个 List 的方法,用起来不太灵活。我们需要自定义构建逻辑,来达到类似链式添加元素的效果。
解决方案
以下提供几种解决方案。
方案一: 使用 @Builder.Default
和 自定义 Builder
-
原理: 我们可以使用
@Builder.Default
注解给inParams
和outParams
字段一个默认值 (空的 ArrayList)。然后,重写 Builder 类中的方法, 以便更灵活的添加inParams
和outParams
元素. -
代码示例:
import lombok.Builder;
import lombok.Data;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
public class DatabaseCallInformation {
private String dbPackage;
private String dbProcedure;
@Builder.Default
private List<InParam> inParams = new ArrayList<>();
@Builder.Default
private List<OutParam> outParams = new ArrayList<>();
@Data
@Builder
public static class InParam {
private String paramName;
private int sqlType;
private String value;
}
@Data
@Builder
public static class OutParam {
private String paramName;
private int sqlType;
// private SqlReturnType sqlReturnType; Assuming you will include, not defined
// private RowMapper<?> rowMapper; Assuming you will include, not defined
}
public static class DatabaseCallInformationBuilder {
private InParamBuilder inParamBuilder;
private OutParamBuilder outParamBuilder;
public InParamBuilder inparam() {
if (inParamBuilder == null) {
inParamBuilder = new InParamBuilder(this);
}
return inParamBuilder;
}
public OutParamBuilder outParam() {
if (outParamBuilder == null) {
outParamBuilder = new OutParamBuilder(this);
}
return outParamBuilder;
}
public DatabaseCallInformationBuilder addInParam(InParam inParam){
this.inParams.add(inParam);
return this;
}
public DatabaseCallInformationBuilder addOutParam(OutParam outParam){
this.outParams.add(outParam);
return this;
}
}
public static class InParamBuilder {
private final DatabaseCallInformationBuilder parent;
private String paramName;
private int sqlType;
private String value;
InParamBuilder(DatabaseCallInformationBuilder parent) {
this.parent = parent;
}
public InParamBuilder paramName(String paramName) {
this.paramName = paramName;
return this;
}
public InParamBuilder sqlType(int sqlType) {
this.sqlType = sqlType;
return this;
}
public InParamBuilder value(String value) {
this.value = value;
return this;
}
public DatabaseCallInformationBuilder and() {
parent.addInParam(new InParam(paramName, sqlType, value));
return parent;
}
}
public static class OutParamBuilder {
private final DatabaseCallInformationBuilder parent;
private String paramName;
private int sqlType;
OutParamBuilder(DatabaseCallInformationBuilder parent) {
this.parent = parent;
}
public OutParamBuilder paramName(String paramName) {
this.paramName = paramName;
return this;
}
public OutParamBuilder sqlType(int sqlType) {
this.sqlType = sqlType;
return this;
}
public DatabaseCallInformationBuilder and() {
parent.addOutParam(new OutParam(paramName, sqlType, null, null)); // replace null with other actual values for returnType, and rowMapper
return parent;
}
}
public static void main(String[] args) {
DatabaseCallInformation result = DatabaseCallInformation.builder()
.dbPackage("MY_PACKAGE")
.dbProcedure("MY_PROCEDURE")
.inparam()
.paramName("ID")
.sqlType(Types.NUMBER)
.and()
.inparam()
.paramName("NAME")
.sqlType(Types.VARCHAR)
.value("TEST_NAME")
.and()
.outParam()
.paramName("result")
.sqlType(Types.VARCHAR)
.and()
.build();
System.out.println(result);
}
}
- 进阶技巧 : 可以创建泛型方法来添加
inParam
和outParam
以减少冗余。//Inside DatabaseCallInformationBuilder private <T> DatabaseCallInformationBuilder addParam(List<T> list, T param) { list.add(param); return this; } public DatabaseCallInformationBuilder addInParam(InParam inParam){ return addParam(this.inParams, inParam); } public DatabaseCallInformationBuilder addOutParam(OutParam outParam){ return addParam(this.outParams, outParam); }
方案二: 使用自定义构建器方法(不重写Lombok的builder方法)
-
原理: 不修改 Lombok 生成的 Builder 类的基本结构,而是创建额外的静态方法,来辅助创建
InParam
和OutParam
对象,并在DatabaseCallInformation
对象的构建过程中将它们添加到列表中。 -
代码示例:
import lombok.Builder;
import lombok.Data;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Data
@Builder
public class DatabaseCallInformation {
private String dbPackage;
private String dbProcedure;
private List<InParam> inParams;
private List<OutParam> outParams;
@Data
@Builder
public static class InParam {
private String paramName;
private int sqlType;
private String value;
}
@Data
@Builder
public static class OutParam {
private String paramName;
private int sqlType;
// private SqlReturnType sqlReturnType; // place holders for required fields, which can't be null in reality
// private RowMapper<?> rowMapper; // place holders for required fields, which can't be null in reality
}
// Static helper methods to build the lists:
public static InParam buildInParam(String paramName, int sqlType, String value) {
return InParam.builder().paramName(paramName).sqlType(sqlType).value(value).build();
}
public static OutParam buildOutParam(String paramName, int sqlType) { // added return types for real fields
return OutParam.builder().paramName(paramName).sqlType(sqlType).build(); // replace the nulls
}
public static void main(String[] args) {
DatabaseCallInformation result = DatabaseCallInformation.builder()
.dbPackage("MY_PACKAGE")
.dbProcedure("MY_PROCEDURE")
.inParams(Arrays.asList(
buildInParam("ID", Types.INTEGER, "123"),
buildInParam("NAME", Types.VARCHAR, "John")
))
.outParams(List.of( // Use List.of for immutability, or Arrays.asList
buildOutParam("result", Types.VARCHAR)
))
.build();
System.out.println(result);
}
}
-
好处 : 这个方案比较简单,不需要修改生成的 Builder 类,只需要添加一些辅助的静态方法。
-
局限性: 它不像第一种方案那样流畅。添加参数的动作是独立的, 不在一个链上.
方案三: 完全自定义Builder
-
原理: 如果对构建过程有非常特殊的需求,Lombok 的
@Builder
无法满足, 就只能完全抛弃Lombok, 完全自己动手实现Builder
类。虽然繁琐,但是拥有最大的灵活性。 -
代码示例: (仅展示关键部分, 需要根据你的实际情况补全)
import lombok.Data;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
@Data //only Data, remove Builder.
public class DatabaseCallInformation {
private String dbPackage;
private String dbProcedure;
private List<InParam> inParams;
private List<OutParam> outParams;
@Data // keep data
@Builder //and builder on sub classes
public static class InParam {
private String paramName;
private int sqlType;
private String value;
}
@Data // keep data
@Builder //and builder on sub classes
public static class OutParam {
private String paramName;
private int sqlType;
// private SqlReturnType sqlReturnType;
// private RowMapper<?> rowMapper;
}
// The completely custom builder.
public static class Builder{
private String dbPackage;
private String dbProcedure;
private final List<InParam> inParams = new ArrayList<>();
private final List<OutParam> outParams = new ArrayList<>();
public Builder dbPackage(String dbPackage) {
this.dbPackage = dbPackage;
return this;
}
public Builder dbProcedure(String dbProcedure) {
this.dbProcedure = dbProcedure;
return this;
}
// inner builders.
public InParamBuilder inParam() {
return new InParamBuilder(this);
}
public OutParamBuilder outParam() {
return new OutParamBuilder(this);
}
public Builder addInParam(InParam param) {
this.inParams.add(param);
return this;
}
public Builder addOutParam(OutParam param) {
this.outParams.add(param);
return this;
}
public DatabaseCallInformation build() {
DatabaseCallInformation info = new DatabaseCallInformation();
info.setDbPackage(dbPackage);
info.setDbProcedure(dbProcedure);
info.setInParams(inParams);
info.setOutParams(outParams);
return info;
}
}
// inner classes
public static class InParamBuilder {
private final Builder parent;
private String paramName;
private int sqlType;
private String value;
public InParamBuilder(Builder parent) {
this.parent = parent;
}
public InParamBuilder paramName(String name){
this.paramName = name;
return this;
}
public InParamBuilder sqlType(int type){
this.sqlType = type;
return this;
}
public InParamBuilder value(String value){
this.value = value;
return this;
}
public Builder and(){
parent.addInParam(new InParam(paramName, sqlType, value));
return parent;
}
}
public static class OutParamBuilder {
private final Builder parent;
private String paramName;
private int sqlType;
public OutParamBuilder(Builder parent) {
this.parent = parent;
}
public OutParamBuilder paramName(String name){
this.paramName = name;
return this;
}
public OutParamBuilder sqlType(int type){
this.sqlType = type;
return this;
}
public Builder and() {
parent.addOutParam(new OutParam(this.paramName, this.sqlType, null, null)); // other params.
return parent;
}
}
// main
public static void main(String[] args) {
DatabaseCallInformation result = new DatabaseCallInformation.Builder()
.dbPackage("my_package")
.dbProcedure("my_procedure")
.inParam()
.paramName("my_in_param")
.sqlType(Types.VARCHAR)
.value("a value")
.and()
.outParam()
.paramName("my_out_param")
.sqlType(Types.INTEGER)
.and()
.build();
}
}
- 注意事项: 自己实现构建器需要仔细考虑所有可能的情况,并编写更多的代码,包括处理默认值、线程安全等问题(如果需要)。
总结
处理 Lombok Builder 中 List 成员的构建问题,主要思路就是自定义构建逻辑。方案一比较推荐,既使用了Lombok, 也增加了构建的灵活性. 具体选择哪种方案,取决于你的项目需求和个人偏好。如果对构建过程没有特别复杂的要求,方案二也可以是一个简洁的选择。方案三, 完全自定义Builder可以应对各种需求,代价是代码较多。