Spring 을 이용해서 프로젝트를 하면 Annotation을 이용해서 쉽게 코드를 작성할 수 있는 lombok 라이브러리를 사용하곤 했습니다. 수 많은 Annotation이 있다보니 정확히 내부적으로 어떻게 동작하는지도 모르고 종종 사용하였습니다. spring의 생성자 주입과 관련된 생성자 annotation에 대해서 헷갈려 정리하려고 합니다.
생성자 관련 Annotation 종류
Lombok에서 생성자 관련 Annotaion은 3가지가 있습니다.
@AllArgsConstructor
@RequiredArgsConstructor
@NoArgsConstructor
하나씩 어떤 조건의 생성자를 만들어주는지 확인해 보겠습니다.
@AllArgsConstructor
영어 뜻 그대로 모든 argument를 가지고 생성자를 만들어 준다는 것을 의미합니다. 여기서 argument는 클래스가 가지고 있는 field라고 생각하면 됩니다. 코드로 예를 들어보겠습니다.
@AllArgsConstructor
public class Person {
private String name;
private int age;
}
위의 코드를 Lombok Annotation을 쓰지 않고서 밑의 코드와 같이 표현할 수 있습니다.
public class Person {
private String name;
private int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
실제로 생성자 만드는 코드가 생성이 되는지 확인해 보겠습니다. build 폴더에서 해당 코드를 찾아서 디컴파일 해주면 코드를 확인할 수 있습니다. 저의 경우 코드를 확인하면 다음과 같이 나옵니다.
public class Person {
private String name;
private int age;
public Person(final String name, final int age) {
this.name = name;
this.age = age;
}
}
@AllArgsConstructor
로 인하여 모든 필드를 초기화해주는 생성자가 만들어집니다.
@AllArgsConstructor
의 경우 모든 필드를 이용하여 생성자를 만들고 싶을 때 사용하는 Annotation입니다.
@RequiredArgsConstructor
영어 뜻처럼 필요한 field를 가지고 생성자를 만들어 준다는 것을 의미합니다. 그럼 여기서 필요한 field란 무엇을 의미하는 것일까요?
Lombok document를 보면 이렇게 나와있습니다.
@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.
위의 글을 보면 final
field와 @NonNull
annotation이 마크된 field를 포함해서 생성자가 만들어진다는 것을 알 수 있습니다. final
field의 경우에는 생성자를 이용하여 초기화해줘야 하기에@RequiredArgsConstrcutor
로 인해 만들어진 생성자에 포함이 되어야 합니다. 그렇다면 @NonNull
은 왜 포함을 시켜줘야 할까요?
@NonNull
도 lombok 라이브러리에 있는 annotation 중 하나입니다. 말 그대로 값이 null이 들어가면 안된다는 뜻입니다. 만약에 생성자에 해당 field가 없는데 접근을 하면 값이 null로 나오기에 생성자에서 초기화를 해줘야 합니다.
코드로 자세히 알아보겠습니다.
@RequiredArgsConstructor
public class Person {
private final String name;
public final int age;
protected final String address;
private String phone;
}
위의 코드를 보면 @RequiredArgsConstructor
가 있으니 final이 붙은 field를 이용하여 생성자가 만들어질 것입니다. build 폴더에서 해당 class를 decompile하여 확인해보겠습니다.
public class Person {
private final String name;
public final int age;
protected final String address;
private String phone;
public Person(final String name, final int age, final String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
final 제어자가 붙은 field로만 이루어진 생성자가 만들어진 것을 볼 수 있습니다.
그럼 이번에 @NonNull를 이용하여 코드를 짜서 확인해 보겠습니다.
@RequiredArgsConstructor
public class Person {
private String name;
public final int age;
protected String address;
@NonNull
private String phone;
}
위의 코드를 보면 age필드에 final
제어자와 phone에 @NonNull
이 마크되어 있습니다. 그럼 해당 2개의 필드로 이루어진 생성자가 생성되는 것을 예상할 수 있습니다. 마찬가지로 build폴더에서 해당 파일을 decompile해서 확인해 보겠습니다.
public class Person {
private String name;
public final int age;
protected String address;
private @NonNull String phone;
public Person(final int age, final @NonNull String phone) {
if (phone == null) {
throw new NullPointerException("phone is marked non-null but is null");
} else {
this.age = age;
this.phone = phone;
}
}
}
위의 코드와 같이 age와 phone field만을 이용하여 생성자가 생성된 것을 볼 수 있습니다.
@RequiredArgsConstructor
의 경우 final
, @NonNull
필드가 있을 때 사용하면 자동으로 생성자를 생성해줍니다. 따라서 직접 생성자 코드를 작성하지 않아도되고 생성자 코드를 작성해야하는데 까먹었을 때도 위의 annotation을 사용하면 오류가 발생하지 않습니다. 하지만 그렇다고 final
, @NonNull
없이 RequiredArgsConstructor
annotation을 사용하면 생성자도 만들어 주지도 않는데 불필요한 코드가 존재하기에 무분별하게 사용해서는 안됩니다.
@NoArgsConstructor
영어 뜻 그대로 아무런 field없이 생성자를 만들어 준다는 것을 의미합니다. 위와 마찬가지로 코드로 예를 들어보겠습니다.
@NoArgsConstructor
public class Person {
private String name;
private int age;
}
위의 코드를 Lombok Annotation을 쓰지 않고서는 밑의 코드와 같이 표현할 수 있습니다. 생성자가 없는데 어떻게 똑같은지 궁금할 수 있습니다. Class에 어떠한 생성자도 존재하지 않을 시에 compiler가 default constructor를 생성해 줍니다.
public class Person {
private String name;
private int age;
}
실제로 위의 두 코드가 똑같은지 빌드 파일을 확인해 보겠습니다.
public class Person {
private String name;
private int age;
public Person() {
}
}
빌드 파일을 확인하면 위의 두 코드 전부 이렇게 decompile이 됩니다.
그럼 여기서 기본 생성자를 만들어 주는데 왜 @NoArgsConstructor
를 사용한지 궁금증이 들 수도 있습니다.
아무런 생성자가 없을 때는 기본적으로 만들어 주지만, 생성자가 이미 존재하거나 @AllArgsConstructor
나 @RequiredArgsConstructor
와 같이 사용할 때도 당연히 생성자가 존재하기에 @NoArgsConstructor
를 이용하여 쉽게 아무 field도 갖고 있지 않은 생성자를 만들어 줄 수 있습니다.
※ 궁금한 사항이나 잘못된 점 댓글 부탁드립니다.
참고자료
https://projectlombok.org/features/constructor
'Spring' 카테고리의 다른 글
[Spring] Spring Container란?(feat: DI, IOC, Singleton) (0) | 2023.01.17 |
---|---|
[Jackson] 왜 field가 1개인 DTO는 기본 생성자가 필요할까?(1) (0) | 2022.12.15 |