简体   繁体   中英

How to run code after constructor in a Lombok builder

I have a class that I want to use Lombok.Builder and I need pre-process of some parameters. Something like this:

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

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

normally I would just call init() on a NoArg constructor, but with the generated builder I'm unable to do so. Is there a way for this init be called by the generated builder? For example build() would generate a code like:

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

I'm aware that I can manually code the all args constructor, that the Builder will call through it and I can call init inside there.

But that is a sub-optimal solution as my class will likely have new fields added every once in a while which would mean changing the constructor too.

After much trial and end error I found a suitable solution: extend the generate builder and call init() myself.

Example:

@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;
      }
   }
}

In Foo you could manually add a constructor, have that do the initialization, and put @Builder on the constructor. I know that you already know this, but I think it is the right solution, and you won't forget to add the parameter since you do want to use the code in the builder anyway.

Disclosure: I am a lombok developer.

I just stumbled upon the same issue. But additionally, I wanted to add an method buildOptional() to the builder to not repeat Optional.of(Foo) each time I need it. This did not work with the approach posted before because the chained methods return FooInternalBuilder objects; and putting buildOptional() into FooInternalBuilder would miss the init() method execution in Builder ...

Also, I personally did not like the presence of 2 builder classes.

Here is what I did instead:

@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;
        }
    }
}

You can do a quick test with this main method:

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);
}

Doing so let's you add what I want:

  • Add an init() method which is executed after each object construction automatically
  • Adding new fields do not require additional work (as it would be for an individually written constructor)
  • Possibility to add additional functionality (incl. the init() execution)
  • Retain the complete standard functionality the @Builder annotation brings
  • Don't expose an additional builder class

Even if you solved your problem before I like to share this as the solution. It is a bit shorter and adds a (for me) nice feature.

This works for me, not a complete solution, but quick and easy.

@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
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM