Как запустить код после конструктора в конструкторе Lombok

У меня есть класс, который я хочу использовать 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.
   }
}

обычно я просто вызываю init() в конструкторе NoArg, но с созданным построителем я не могу этого сделать. Есть ли способ вызвать это init сгенерированным построителем? Например, build() сгенерирует такой код:

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

Я знаю, что могу вручную закодировать конструктор all args, который будет вызывать через него Builder, и я могу вызвать init внутри него.

Но это неоптимальное решение, поскольку в мой класс, вероятно, время от времени будут добавляться новые поля, что также будет означать изменение конструктора.


person Budius    schedule 22.06.2016    source источник


Ответы (4)


После долгих проб и ошибок я нашел подходящее решение: расширить конструктор генерации и вызвать 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;
      }
   }
}
person Budius    schedule 22.06.2016
comment
проблема с этим подходом в том, что он не очень хорошо работает с toBuilder(), но с остальным все в порядке. - person Budius; 22.06.2016
comment
Вам не нужно указывать builderClassname. Если в вашем коде есть класс FooBuilder, lombok просто добавит методы в этот класс. - person Roel Spilker; 23.06.2016
comment
Это интересно. Я сделаю несколько тестов и проверю. - person Budius; 23.06.2016
comment
Действительно интересная реализация. Спасибо. Поскольку вся идея состоит в том, чтобы дополнить построитель, не следует ли определять init () как часть Builder? - person Soumen; 19.10.2016
comment
Класс построителя должен быть частным ИМХО, поскольку вы создаете его с помощью построителя. Я бы попытался установить построитель с пакетом уровня доступа, чтобы избежать создания родительского построителя. Из документации Lombok: @Builder (access = AccessLevel.PACKAGE) является законным (и будет генерировать класс построителя, метод построителя и т. Д. С указанным уровнем доступа), начиная с lombok v1.18.8. - person David Barda; 20.06.2020

В Foo вы можете вручную добавить конструктор, выполнить инициализацию и поместить @Builder в конструктор. Я знаю, что вы это уже знаете, но я думаю, что это правильное решение, и вы не забудете добавить параметр, поскольку все равно хотите использовать код в конструкторе.

Раскрытие информации: я разработчик ломбока.

person Roel Spilker    schedule 23.06.2016
comment
привет Роэл, я рада, что ты зашел, чтобы ответить. Спасибо за библиотеку. Если я создам свой собственный конструктор, это означает, что мне придется вручную создавать конструктор all-arg и поддерживать его каждый раз, когда появляются новые поля. Пытаясь найти больше, я нашел этот запрос функции в библиотеке github.com/rzwitserloot/lombok/issues/674 и я думаю, что это действительно то, что я хотел. - person Budius; 23.06.2016

Я только что наткнулся на ту же проблему. Но, кроме того, я хотел добавить в конструктор метод buildOptional(), чтобы не повторять Optional.of(Foo) каждый раз, когда он мне нужен. Это не сработало с подходом, опубликованным ранее, потому что связанные методы возвращают FooInternalBuilder объектов; а установка buildOptional() в FooInternalBuilder приведет к пропуску выполнения метода init() в _7 _...

Также мне лично не понравилось наличие 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;
        }
    }
}

Вы можете провести быструю проверку с помощью этого основного метода:

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
  • Не выставляйте дополнительный класс построителя

Даже если вы решили свою проблему раньше, я хочу поделиться этим как решением. Он немного короче и добавляет (для меня) приятную особенность.

person J.R.    schedule 19.01.2018
comment
Здесь вы показываете внутреннюю сборку, которая на самом деле не является внутренней. - person David Barda; 17.06.2020
comment
@DavidBarda: Я действительно хочу показать build() и buildOptional() (в качестве удобного метода), потому что обе функции могут использоваться во всем коде. Или вы имеете в виду другое? - person J.R.; 18.06.2020
comment
А как насчет раскрытия метода buildInternal ()? - person David Barda; 18.06.2020
comment
@DavidBarda: А, ладно. Это (очень) небольшая цена, которую я готов заплатить за общий подход. Я согласен с тем, что это не на 100% идеально, но использование Builder такое, как и следовало ожидать: использование метода build() для получения объекта. Лично я считаю внутреннее предупреждение не использовать этот метод. Конечно, мы могли бы его переименовать, но получить отрицательный голос за другое мнение о наименовании довольно сложно (для меня). - person J.R.; 19.06.2020
comment
Вот что я сделал: 1. Переименовал buildInternal в buildInitial 2. Изменил тип возвращаемого значения для init с void на Foo 3. Изменил тело метода build на just: return this.buildInitial (). Init (); Нет особой разницы, только немного чище. - person Ivhani Maselesele; 18.02.2021

Это работает для меня, не полное решение, но быстро и легко.

@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
   }
}
person Marc    schedule 09.03.2019