【问题标题】:How to run code after constructor in a Lombok builder如何在 Lombok 构建器中的构造函数之后运行代码
【发布时间】:2016-06-22 12:55:11
【问题描述】:

我有一个要使用 Lombok.Builder 的类,我需要对一些参数进行预处理。像这样的:

@Builder
public class Foo {
   public String val1;
   public int val2;
   public List<String> listValues;

   public void init(){
       // do some checks with the values.
   }
}

通常我只会在 NoArg 构造函数上调用init(),但使用生成的构建器我无法这样做。有没有办法让生成的构建器调用这个init?例如build() 会生成如下代码:

public Foo build() {
   Foo foo = Foo(params....)
   foo.init();
   return foo;
}

我知道我可以手动编写 all args 构造函数,Builder 将通过它调用它,我可以在那里调用 init

但这是一个次优的解决方案,因为我的班级可能每隔一段时间就会添加新字段,这意味着也要更改构造函数。

【问题讨论】:

    标签: java lombok


    【解决方案1】:

    Foo 中,您可以手动添加一个构造函数,让它进行初始化,然后将@Builder 放在构造函数上。我知道您已经知道这一点,但我认为这是正确的解决方案,并且您不会忘记添加参数,因为您确实想使用构建器中的代码。

    披露:我是龙目岛开发人员。

    【讨论】:

    • 嗨,Roel,很高兴您过来回答。感谢您的库。如果我创建自己的构造函数,这意味着我必须手动创建一个“全参数”构造函数,并在每次有新字段时维护它。搜索更多,我在 lib github.com/rzwitserloot/lombok/issues/674 上找到了这个功能请求,我想这就是我想要的。
    • 覆盖 build() 对于具有少量字段的类是可以的。但是对于任何复杂的 dto 来说,维护起来真的很痛苦。如果字段有字符串或原语,那就更糟了——如果构造函数中的字段顺序发生了变化,那就是一场灾难。
    • 你拯救了我的一天@Roel Spilker
    【解决方案2】:

    经过多次反复试验,我找到了一个合适的解决方案:扩展生成构建器并自己调用init()

    例子:

    @Builder(toBuilder = true, builderClassName = "FooInternalBuilder", builderMethodName = "internalBuilder")
    public class Foo {
    
       public String val1;
       public int val2;
       @Singular public List<String> listValues;
    
       void init() {
          // perform values initialisation
       }
    
       public static Builder builder() {
          return new Builder();
       }
    
       public static class Builder extends FooInternalBuilder {
    
          Builder() {
             super();
          }
    
          @Override public Foo build() {
             Foo foo = super.build();
             foo.init();
             return foo;
          }
       }
    }
    

    【讨论】:

    • 这种方法的问题在于它不能很好地与toBuilder() 一起使用,但其余的都很好。
    • 您无需提供builderClassname。如果您的代码中有 FooBuilder 类,lombok 只会将方法添加到该类。
    • 这很有趣。我会做一些测试并检查一下。
    • 真正有趣的实现。谢谢。由于整个想法是补充构建器,所以不应该将 init() 定义为构建器的一部分吗?
    • 构建器类应该是私有的恕我直言,因为您正在使用构建器创建它。我会尝试使用访问级别包设置构建器,以避免创建父构建器。来自 Lombok 文档:@Builder(access = AccessLevel.PACKAGE) 从 lombok v1.18.8 开始是合法的(并将生成具有指示访问级别的构建器类、构建器方法等)。
    【解决方案3】:

    我只是偶然发现了同样的问题。但另外,我想在构建器中添加一个方法buildOptional(),以便在每次需要时不重复Optional.of(Foo)。这不适用于之前发布的方法,因为链式方法返回 FooInternalBuilder 对象;并将buildOptional() 放入FooInternalBuilder 将错过Builder 中的init() 方法执行...

    另外,我个人不喜欢 2 个构建器类的存在。

    这是我所做的:

    @Builder(buildMethodName = "buildInternal")
    @ToString
    public class Foo {
        public String val1;
        public int val2;
        @Singular  public List<String> listValues;
    
        public void init(){
            // do some checks with the values.
        }    
    
        /** Add some functionality to the generated builder class */
        public static class FooBuilder {
            public Optional<Foo> buildOptional() {
                return Optional.of(this.build());
            }
    
            public Foo build() {
                Foo foo = this.buildInternal();
                foo.init();
                return foo;
            }
        }
    }
    

    你可以用这个 main 方法做一个快速测试:

    public static void main(String[] args) {
        Foo foo = Foo.builder().val1("String").val2(14)
                .listValue("1").listValue("2").build();
        System.out.println(foo);
    
        Optional<Foo> fooOpt = Foo.builder().val1("String").val2(14)
                .listValue("1").listValue("2").buildOptional();
        System.out.println(fooOpt);
    }
    

    这样做让我们添加我想要的内容:

    • 添加一个init() 方法,在每个对象构造后自动执行
    • 添加新字段不需要额外的工作(就像单独编写的构造函数一样)
    • 可以添加额外的功能(包括init() 执行)
    • 保留完整 @Builder 注解带来的标准功能
    • 不要公开额外的构建器类

    即使您在我喜欢将其作为解决方案分享之前解决了您的问题。它有点短,并增加了一个(对我来说)不错的功能。

    【讨论】:

    • 你在这里暴露了一个内部的构建,它实际上不是内部的
    • @DavidBarda :我确实想公开build()buildOptional()(作为一种方便的方法),因为这两个函数都可能在整个代码中使用。还是您的意思不同?
    • 暴露 buildInternal() 方法怎么样?
    • @DavidBarda:啊,好的。这是我愿意为通用方法付出的(非常)小代价。我同意这不是 100% 理想的,但 Builder 的使用与人们期望的一样:使用 build() 方法获取对象。就我个人而言,我将“内部”视为不使用该方法的警告。当然,我们可以重命名它,但对命名的不同意见投反对票(对我来说)是相当困难的。
    • 这是我所做的: 1. 将 buildInternal 重命名为 buildInitial 2. 将 init 的返回类型从 void 更改为 Foo 3. 将 build 的方法体更改为 just:return this.buildInitial().init() ;差别不大,就是干净了一点。
    【解决方案4】:

    这对我有用,不是一个完整的解决方案,但又快又容易。

    @Builder
    @AllArgsConstructor
    public class Foo {
       @Builder.Default
       int bar = 42;
       Foo init() {
          // perform values initialisation
         bar = 451;   // replaces 314
         return foo;
       }
       static Foo test() {
           return new FooBuilder()  // defaults to 42
               .bar(314)  // replaces 42 with 314
               .build()
               .init();   // replaces 314 with 451
       }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-27
      • 2016-11-13
      • 1970-01-01
      • 1970-01-01
      • 2020-12-09
      • 1970-01-01
      相关资源
      最近更新 更多