본문 바로가기

Tech/Spring | SpringBoot

[Spring] lombok - @Builder

반응형

1. @Builder

@Builder 는 객체에 Builder 패턴을 자동으로 적용해주는 lombok 의 어노테이션이다. Builder 패턴은 복잡한 객체를 간단하게 생성할 수 있도록하는 디자인 패턴 중 하나로 생성자를 통한 생성이 아닌 빌더의 필드별 함수를 통해서 객체를 생성한다.

@Builder 는 클래스 레벨에 붙이거나 생성자 또는 메서드에 붙여서 파라미터를 활요한 Builder 패턴을 자동으로 생성해준다.

2. @Builder class

만약에 클래스가 @Builder 로 지정된다면 클래스의 모든 필드를 매개변수로 받는 private 생성자가 자동으로 생성된다. 이때 생성자는 컴파일러를 통해 자동으로 생성되기 때문에 사용자가 따로 생성자를 구현하거나 @XArgsConstructor 형식의 어노테이션을 통해서 생성자를 생성하도록 하면 안된다. 만약 생성자가 @Builder 외에 다른 방식으로 생성되는 경우 lombok 은 all-args 생성자가 이미 존재하는 것으로 판단하고 해당 생성자를 사용하도록 한다. 그렇게 되면 존재하지 않는 생성자를 사용하게 되면서 컴파일 에러를 발생시킨다.

 

@Builder 를 사용하게 되면 해당 클래스에 private 생성자와 클래스 이름 뒤에 Builder 가 붙은 TBuilder 라는 inner class 가 생성된다. builder() 라는 메서드도 클래스에 추가되는데 이 메서드를 통해서 inner class 인 TBuilder 를 만들 수 있다.

 

빌더 클래스는 @Builder 로 지정된 생성자나 메서드, 그리고 클래스의 각 파라미터, 필드 별로 하나씩 메서드가 생성된다. 이 메서드는 빌더 클래스 자신을 반환하는데, 이 메서드를 통해서 setter 와 같이 각 파라미터 또는 필더의 값을 입력할 수 있다. 빌더 클래스는 build() 라는 메서드도 가지고 있는데, 이 메서드를 통해서 입력된 값들을 가지는 원본 클래스의 객체를 생성하여 반환한다.

- @Builder 클래스 결과 코드

아래의 예제는 실제로 @Builder 어노테이션을 클래스에 붙였을 때 어떤 코드가 생성되는지 예제로 표현한 것이다.

 

 @Builder
 class Example<T> {
        private T foo;
        private final String bar;
 }

 

위의 코드는 Example 이라는 클래스에 @Builder 어노테이션은 지정한 코드이다. Example 코드는 foo bar 라는 두개의 필드를 가지고 있으며 따로 생성자를 구현하지 않았다. 아래의 코드는 컴파일이 실행되었을 때 @Builder 어노테이션을 통해서 생성되는 Example 코드이다.

 

class Example<T> {
        private T foo;
        private final String bar;
       
        private Example(T foo, String bar) {
                this.foo = foo;
                this.bar = bar;
        }
       
        public static <T> ExampleBuilder<T> builder() {
                return new ExampleBuilder<T>();
        }
       
        public static class ExampleBuilder<T> {
                private T foo;
                private String bar;
               
                private ExampleBuilder() {}
               
                public ExampleBuilder foo(T foo) {
                        this.foo = foo;
                        return this;
                }
               
                public ExampleBuilder bar(String bar) {
                        this.bar = bar;
                        return this;
                }
               
                @java.lang.Override public String toString() {
                        return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")";
                }
               
                public Example build() {
                        return new Example(foo, bar);
                }
        }
 }

 

컴파일을 통해서 생성된 Example 클래스를 보면 모든 필드를 가지는 private 생성자와 inner class ExampleBuilder 클래스를 반환하는 builder() 메서드가 생성되었다.

 

inner class Example 객체를 생성하기위한 빌더, ExampleBuilder 클래스가 생성되었다. ExampleBuilder Example 클래스와 동일한 필드들을 가지고 있으며, 각 필드들은 동명의 메서드 (setter) 를 통해서 값을 입력받을 수 있도록 되어있다. 입력받은 필드를 통해서 Example 객체를 생성하여 반환하는 build() 메서드도 구현되었다.

 

한편 Example 클래스의 bar 필드는 final 필드이다. 그렇기 때문에 Builder 가 아닌 생성자를 통한 객체 생성시에는 무조건 필드의 값이 초기화가 되어야 하고, 한번 초기화 되면 값을 변경할 수 없다. 하지만 @Builder 가 적용된 것을 확인하면 bar 필드가 final 이 아닌 일반 필드로 선언된 것을 확인할 수 있다. 그 이유는 빌더 클래스가 private, non-static, non-final 속성을 가지기 때문이다. 그렇기 때문에 원본 클래스에서 final 로 선언을 하여도 빌더가 적용되면 null 값으로 초기화하거나 값을 계속 변경할 수 있게 된다. 만약 final 성질을 가져야 하는 경우에는 @Builder.Default 속성을 사용하거나 선언 시점, 또는 생성자에서 초기화하도록 해야한다.

3. @Builder constructor

앞서 설명한 것과 같이 @Builder 는 클래스뿐만 아니라 생성자에도 붙일 수 있다. 생성자에 @Builder 어노테이션이 붙었을 때 실제로 자동 생성되는 클래스는 이전의 클래스의 경우와 유사하다. 한가지 차이점은 클래스 레벨의 경우 모든 필드에 대하여 빌더 클래스에 메서드가 생겼다면 생성자 레벨의 경우 생성자의 파라미터 필드들만 빌더 메서드가 생성된다.

[Reference]

- https://highmyposition.oopy.io/coding/java/builder-pattern

 

@Builder와 멤버 변수 초기화

자바 객체 생성과 빌더 패턴, 그리고 @Builder 어노테이션에서의 멤버 변수 초기화

highmyposition.oopy.io

- https://velog.io/@park2348190/Lombok-Builder의-동작-원리

 

Lombok @Builder의 동작 원리

보일러플레이트 메서드(getter/setter, constructor 등)를 직접 작성하지 않아도 대신 작성해주는 Lombok를 최근에 많이 활용하고 있다. 그나마 setter 메서드같은 경우는 값을 변경시키는 메서드는 그 목

velog.io

- https://projectlombok.org/api/lombok/Builder

 

Builder (Lombok)

setterPrefix java.lang.String setterPrefix Prefix to prepend to 'set' methods in the generated builder class. By default, generated methods do not include a prefix. For example, a method normally generated as someField(String someField) would instead be g

projectlombok.org

 

반응형