@RequiredArgsConstructor와 @AllArgsConstructor 비교하기
문제 상황
프로젝트 진행 중에 Mapstruct 라이브러리를 사용하여 DTO를 자동 매핑하는 과정에서 문제를 겪었습니다.
기존에는 무조건 DTO 필드를 @AllArgsConstructor로 설정했었는데, DTO의 특성상 불변 객체로 만드는 것이 더 적합하다고 판단해서 @RequiredArgsConstructor로 변경했었습니다.
하지만 @RequiredArgsConstructor로 설정된 클래스의 필드가 누락되어 매핑되지 않는 오류가 발생했고 해결책을 찾기 위해 DTO 클래스를 디컴파일해 본 결과, 문제의 원인을 찾을 수 있었습니다.
Lombok 어노테이션 비교
Lombok의 @RequiredArgsConstructor와 @AllArgsConstructor는 각각 생성자를 자동으로 생성해주는 편리한 기능을 제공합니다. 하지만 두 어노테이션의 동작 방식의 차이가 존재하는데, 아래에서 각각의 차이를 살펴보겠습니다.
Lombok 공식 사이트에 의하면 두 어노테이션의 설명은 아래와 같습니다.
@RequiredArgsConstructor
@RequiredArgsConstructor generates a constructor with 1 parameter for each field that requires special handling. All non-initialized final fields get a parameter, as well as any fields that are marked as @NonNull that aren't initialized where they are declared. For those fields marked with @NonNull, an explicit null check is also generated. The constructor will throw a NullPointerException if any of the parameters intended for the fields marked with @NonNull contain null. The order of the parameters match the order in which the fields appear in your class.
@RequiredArgsConstructor는 다음과 같은 방식으로 생성자를 생성합니다.
- 필드 선택:
- 초기화되지 않은 final 필드
- 선언 시 초기화되지 않은 @NonNull로 표시된 필드
- 생성자 매개변수:
- 위 조건에 해당하는 필드들만 매개변수로 가지는 생성자를 생성합니다.
- null 체크:
- @NonNull 로 표시된 필드에 대해 null 체크를 수행하며, null 값이 전달되면 NullPointerException을 발생시킵니다.
- 매개변수 순서:
- 생성자 매개변수의 순서는 클래스 내에서 필드가 선언된 순서를 따릅니다.
@AllArgsConstructor
@AllArgsConstructor generates a constructor with 1 parameter for each field in your class. Fields marked with
@NonNull result in null checks on those parameters.
@AllArgsConstructor 는 다음과 같은 방식으로 생성자를 생성합니다.
- 필드 선택:
- 클래스의 모든 필드가 매개변수로 포함됩니다.
- null 체크:
- @NonNull로 표시된 필드에 대해 null 체크를 수행하며, null 값이 전달되면 NullPointerException을 발생시킵니다.
구현 및 디컴파일
구현 java 코드
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
public class AnnotationTest {
@AllArgsConstructor
public static class AllArgsConstructorDto {
private String field_1;
@NonNull
private String field_2;
private final String field_3;
private final String field_4;
}
@RequiredArgsConstructor
public static class RequiredArgsConstructorDto {
private String field_5;
@NonNull
private String field_6;
private final String field_7;
private final String field_8;
}
}
디컴파일한 class 파일 코드
@AllArgsConstructor 결과
// AllArgsConstructorDto 디컴파일 소스
public static class AllArgsConstructorDto {
private String field_1;
private @NonNull String field_2;
private final String field_3;
private final String field_4;
public AllArgsConstructorDto(final String field_1, final @NonNull String field_2,
final String field_3, final String field_4)
{
if (field_2 == null) {
throw new NullPointerException("field_2 is marked non-null but is null");
} else {
this.field_1 = field_1;
this.field_2 = field_2;
this.field_3 = field_3;
this.field_4 = field_4;
}
}
}
@AllArgsConstructor가 적용된 AllArgsConstructorDto 클래스는 모든 필드를 매개변수로 가지는 생성자를 생성합니다. final 키워드와 상관없이 모든 필드가 생성자에 포함되며, @NonNull 어노테이션이 붙은 field2에 대해서는 null 체크가 추가되는 것을 확인할 수 있습니다.
@RequiredArgsConstructor 결과
// RequiredArgsConstructorDto 디컴파일 소스
public static class RequiredArgsConstructorDto {
private String field_5;
private @NonNull String field_6;
private final String field_7;
private final String field_8;
public RequiredArgsConstructorDto(final @NonNull String field_6,
final String field_7, final String field_8)
{
if (field_6 == null) {
throw new NullPointerException("field_6 is marked non-null but is null");
} else {
this.field_6 = field_6;
this.field_7 = field_7;
this.field_8 = field_8;
}
}
}
@RequiredArgsConstructor가 적용된 RequiredArgsConstructorDto 클래스는 final 키워드가 붙은 필드(field7 , field8)와 @NonNull 로 표시된 필드(field6 )만을 매개변수로 가지는 생성자를 생성합니다. final이 아닌 field5 는 생성자 매개변수에 포함되지 않는 것을 확인할 수 있습니다. 이와 같이 final 필드가 아니더라도 @NonNull 어노테이션이 붙어 있는 필드도 생성자의 매개변수로 추가됩니다.
결론
Lombok의 @RequiredArgsConstructor와 @AllArgsConstructor를 사용한다면 자동으로 생성자를 생성해줘서 편리하게 사용할 수 있지만, 각각의 어노테이션이 필드에 접근하는 방식이 다르기 때문에 어떤 어노테이션을 사용할지 선택할 때는 클래스의 요구사항에 맞게 신중하게 결정해야 합니다.
@RequiredArgsConstructor는 필드 중 특정 조건을 충족하는 필드만 생성자에 포함시키는 반면, @AllArgsConstructor는 클래스의 모든 필드를 생성자에 포함시킵니다.
이러한 차이를 이해하고 사용하면, 개발 중에 발생할 수 있는 매핑 오류나 다른 문제를 예방할 수 있습니다.