티스토리 뷰
@Builder 사용 시 발생했던 에러에 대해 다뤄보려고 합니다.
@Builder란?
반복되는 코드를 줄여주는 라이브러리인 Lombok에서 제공하는 어노테이션으로
빌더 패턴을 직접 구현하지 않더라도 사용할 수 있습니다.
보통 클래스, 생성자 위에 붙여 사용합니다.
그래서 왜 조심해야 할까?
개발 환경은 다음과 같습니다.
IntelliJ
Spring Boot 2.7.4
첫 번째
먼저 예시를 들어보겠습니다!
@ToString
@Builder
public class User {
private String name = "***님";
private String password;
private List<String> books = new ArrayList<>();
}
public class Main {
public static void main(String[] args) {
User user = User.builder()
.password("1234")
.build();
System.out.println(user.toString());
}
}
name, books는 초깃값이 할당되어 있기 때문에 passowrd만 설정하는 상황입니다.
출력 결과는User(name=null, password=1234, books=null)이 나옵니다.
메인 함수에서 생성할 때 명시하지 않은 필드는 초깃값으로 초기화 되기를 기대했지만 null로 초기화되었습니다.
이것을 모르고 사용하다가 NullPointerexception이 발생했습니다...
public class User {
private String name = "***님";
private String password;
private List<String> books = new ArrayList();
...
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder { // <--- @Builder가 생성해줌
// User에서는 초깃값을 명시해도 UserBuilder는 명시되지 않음
private String name;
private String password;
private List<String> books;
UserBuilder() {
}
public UserBuilder name(final String name) {
this.name = name;
return this;
}
public UserBuilder password(final String password) {
this.password = password;
return this;
}
public UserBuilder books(final List<String> books) {
this.books = books;
return this;
}
public User build() {
return new User(this.name, this.password, this.books);
}
}
우리 눈엔 보이지 않는 빌드 후 생성된 User.class입니다.@Builder로 인해 Inner Class로 UserBuilder가 생긴 것을 확인할 수 있습니다.
User에서는 초깃값을 명시해도 UserBuilder는 명시되지 않습니다.
그래서 .password("1234")를 제외한 나머지 필드는 초기화하지 않았기 때문에 wrapper class default value인 null로 초기화됩니다.
해결 방법
@Builder.Default 사용하기
@ToString
@Builder
public class User {
@Builder.Default
private String name = "***님";
private String password;
@Builder.Default
private List<String> books = new ArrayList<>();
}
@Builder.Default를 사용하면, User(name=***님, password=1234, books=[]) 출력이 잘 됩니다!
이유는?!?!
public class User {
private String name;
private String password;
private List<String> books;
private static String $default$name() {
return "***님";
}
private static List<String> $default$books() {
return new ArrayList();
}
...
public static UserBuilder builder() {
return new UserBuilder();
}
public static class UserBuilder {
private boolean name$set;
private String name$value;
private String password;
private boolean books$set;
private List<String> books$value;
...
public UserBuilder name(final String name) {
this.name$value = name;
this.name$set = true;
return this;
}
public UserBuilder password(final String password) {
this.password = password;
return this;
}
public UserBuilder books(final List<String> books) {
this.books$value = books;
this.books$set = true;
return this;
}
public User build() {
String name$value = this.name$value;
if (!this.name$set) {
name$value = User.$default$name();
}
List<String> books$value = this.books$value;
if (!this.books$set) {
books$value = User.$default$books();
}
return new User(name$value, this.password, books$value);
}
}
}
@Builder.Default 사용 후 만들어진 User.class입니다.
이제 .name(), .books()를 호출하지 않는다면 $default$name(), $default$books()로 초깃값이 지정됩니다.
초기화 관련 로직이 추가되었기 때문에 더 이상 wrapper class default value인 null이 안 들어가게 됩니다!
두 번째
상황
자식 클래스 생성자에서 @Builder를 사용한 부모 클래스에 값을 넘기고 싶어 super를 사용했지만,
패키지 위치가 달라 컴파일 에러가 발생했습니다.
코드 뜯어보기
생성자를 명시하지 않으면 어디서든 접근할 수 있는 @AllArgsConstructor(access = AccessLevel.PUBLIC)이 생성될 줄 알았지만,

// 컴파일된 User.class에서는 default 생성자가 생성됨
User(final String name, final String password, final List<String> books) {
this.name = name;
this.password = password;
this.books = books;
}
같은 패키지에서만 접근할 수 있는 @AllArgsConstructor(access = AccessLevel.PACKAGE)가 생성됩니다.
해결 방법
따라서 @Builder와 모든 필드를 가지는 생성자를 같이 사용하고 싶다면
직접 생성자 만들기 (추천)
@RequiredArgsConstructor (추천)
@AllArgsConstructor (비추천)
세 가지 방법 중 하나를 선택해면 됩니다.
마무리
@Builder관련 자료를 찾아보면서 @Builder.Default, @builder(tobuilder = true) 등 다양한 기능이 있다는 것을 알게 되었습니다.
또한 Lombok의 장점으로 반복되는 코드를 줄여 가독성을 높여주지만, 제대로 학습하지 않고 사용하면 사이드 이펙트가 생길 수 있다는 것을 알게 되었습니다.
'Spring' 카테고리의 다른 글
| Spring Boot @Transactional propagation 속성 (0) | 2024.08.30 |
|---|---|
| JPA N + 1 원인 및 해결 방법 (1) | 2024.08.02 |
| Spring Boot Gradle 멀티 모듈 사용하기 (0) | 2024.07.29 |
| Kotlin + Spring 프로젝트에 ktlint 도입하기 (1) | 2024.06.11 |
| Webhook을 사용해 slack 메세지 보내기 (2) | 2024.06.04 |
- Total
- Today
- Yesterday
- 성능 개선
- Spring Boot
- multi module
- 헥사고날 아키텍처
- 자동화
- propagation
- transaction
- lint
- lombok
- Slack
- 테스트 코드
- ktlint
- IDE
- 계층형 아키텍처
- detekt
- 인텔리제이
- Gradle
- H2
- JPA
- N + 1
- webhook
- embedded redis
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
| 29 | 30 | 31 |