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
- https://velog.io/@park2348190/Lombok-Builder의-동작-원리
- https://projectlombok.org/api/lombok/Builder
'Tech > Spring | SpringBoot' 카테고리의 다른 글
[SpringBoot] Logging - 3 (log4j2) (1) | 2024.01.13 |
---|---|
[Spring] JaCoCo (0) | 2023.02.23 |
[Spring] MapStruct (1) | 2023.01.08 |
[Spring] ResponseEntity (0) | 2022.12.31 |
[SpringBoot] 예외처리 - 3 (@ExceptionHandler, @ResponseStatus, @ControllerAdvice) (0) | 2022.12.20 |