Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run code after constructor in a Lombok builder

Tags:

java

lombok

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.

like image 819
Budius Avatar asked Jun 22 '16 12:06

Budius


People also ask

How do you use the Lombok constructor?

Create a Static Factory Method with @AllArgsConstructor When staticName is used, Lombok marks the generated constructor as private and creates a static factory method - of() in our case - that will be used to construct all User objects.

What is @builder annotation in Lombok?

Lombok's @Builder annotation is a useful technique to implement the builder pattern that aims to reduce the boilerplate code. In this tutorial, we will learn to apply @Builder to a class and other useful features. Ensure you have included Lombok in the project and installed Lombok support in the IDE.

What does @builder do in Lombok?

When we annotate a class with @Builder, Lombok creates a builder for all instance fields in that class. We've put the @Builder annotation on the class without any customization. Lombok creates an inner static builder class named as StudentBuilder. This builder class handles the initialization of all fields in Student.

What is the use of @builder annotation in spring boot?

The @Builder annotation produces complex builder APIs for your classes. @Builder lets you automatically produce the code required to have your class be instantiable with code such as: Person. builder()


3 Answers

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.

like image 82
Roel Spilker Avatar answered Oct 23 '22 08:10

Roel Spilker


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;
      }
   }
}
like image 34
Budius Avatar answered Oct 23 '22 10:10

Budius


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.

like image 7
J.R. Avatar answered Oct 23 '22 09:10

J.R.