생성자 주입(Constructor Injection)은 의존성 주입의 한 종류로, 객체를 생성할 때 해당 객체가 필요로 하는 의존성을 생성자를 통해 주입하는 방식이에요. 스프링 프레임워크에서는 Autowired 어노테이션을 사용합니다. 반면에 필드 주입(Field Injection)은 의존성을 클래스의 필드에 직접 주입하는 방식이며, Autowired 어노테이션을 필드에 적용합니다.
@Service
public clas ExampleService {
private final ExampleRepository exampleRepository;
@Autowired
public ExampleService(ExampleRepository exampleRepository) {
this.exampleRepository = exampleRepository;
}
}
위아래의 두 코드는 Autowired 어노테이션이 적용 유/무이 차이가 있는데, 놀랍게도 두 코드는 동일한 동작을 수행해요 🤔🤔
첫 번째 코드에서는 스프링 프레임워크에게 해당 생성자를 사용하여 ExampleRepository 의존성을 주입하라는 것을 알려줍니다. 반면에 두 번째 코드에서는 Autowired 어노테이션이 없습니다. 그러나 스프링 프레임워크는 여전히 생성자 주입을 사용하여 ExampleRepository 의존성을 주입할 것입니다.
스프링 프레임워크에서는 생성자 주입을 적극 지원하고 있기 때문에, 생성자가 1개만 있을 경우에 @Autowired를 생략해도 주입이 가능하도록 편의성을 제공하고 있습니다. 그렇기 때문에 위아래 코드는 아래와 동일한 코드가 돼요 🙄🙄
@Service
public clas ExampleService {
private final ExampleRepository exampleRepository;
public ExampleService(ExampleRepository exampleRepository) {
this.exampleRepository = exampleRepository;
}
}
그런데 저는 보통 위 와 같이 필드 주입이나 Autowired 어노테이션을 사용해서 생성자를 생성하지 않고, 롬복(Lombok) 라이브러리를 사용하여 더욱 편리하게 생성자를 주입을 합니다. 😊😊
@NoArgsConstructor
@NoArgsConstructor 어노테이션은 파라미터가 없는 디폴트 생성자를 자동으로 생성해 줘요. 이 어노테이션을 사용하면, 클래스에 명시적으로 선언된 생성자가 없더라도 인스턴스를 생성할 수 있습니다.
1-1) 상속 클래스와 함께 사용 시, 올바르지 않은 코드
import lombok.NoArgsConstructor;
public class ParentClass {
public ParentClass(String name) {
// 생성자 로직
}
}
@NoArgsConstructor
public class ChildClass extends ParentClass {
// 생성자가 없음
}
하지만 위와 같이 ChildClass가 ParentClass를 상속받고 있지만, NoArgsConstructor 어노테이션을 사용하여 자동으로 생성된 기본 생성자가 부모 클래스의 생성자를 호출하지 않으므로 컴파일 오류가 발생합니다. 위와 같은 상황에서는 아래와 같이 코드 수정이 필요해요.
1-2) 상속 클래스와 함께 사용 시, 올바른 코드
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class ParentClass {
public ParentClass(String name) {
// 생성자 로직
}
}
@NoArgsConstructor
public class ChildClass extends ParentClass {
}
부모 클래스에도 동일하게 기본 생성자를 생성하고 있으므로 상속 관계에서도 문제가 발생하지 않습니다 😊
2-1) 의존성 주입을 할 때, 올바르지 않은 코드
그리고 의존성 주입과 함께 사용할 때도 주의해줘야 해요. 아래 예시 코드를 한번 볼까요? 🤨
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@NoArgsConstructor
public class ExampleService {
private final ExampleRepository exampleRepository;
// 생성자가 없음
// 서비스 메서드들...
}
위에 예시 코드에서 ExampleService 클래스는 스프링의 서비스로 사용되지만 NoArgsConstructor 어노테이션을 사용하여 자동 생성된 기본 생성자가 없습니다. 이 경우 스프링이 해당 클래스의 의존성 주입할 수가 없습니다. 이럴 때는 NoArgsConstructor 어노테이션을 사용하지 않고 RequiredArgsConstructor 어노테이션 또는 AllArgsConstructor 어노테이션을 사용해줘야 합니다. 🤔
물론 아래 코드처럼 force=true 옵션을 사용하면 모든 final 필드는 0 / false / null로 초기화할 수 있어요.
@NoArgsConstructor(force = true)
그런데 filed에 final이 아닌 @NonNull 같은 제약이 있는 어노테이션이 붙어 있다면, 해당 옵션을 주어도 생성자에 들어가지 않기 때문에 이점 주의해줘야 합니다 🙄🙄
2-2) 의존성 주입할 때, 올바른 코드
RequiredArgsConstructor 어노테이션 또는 AllArgsConstructor 어노테이션을 사용하여 필드 주입을 허용하고, 스프링이 해당 클래스의 의존성을 주입할 수 있게 됩니다. 🧑💻
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ExampleService {
private final ExampleRepository exampleRepository;
// 생성자가 없어도 Spring이 의존성을 주입함
// 서비스 메서드들...
}
@RequiredArgsConstructor
RequiredArgsConstructor 어노테이션은 final이나 @NonNull으로 선언된 필드만을 파라미터로 받는 생성자를 자동으로 생성해 줍니다. 이 어노테이션을 사용하면, 클래스가 의존하는 필드를 간단하게 초기화할 수 있어요.
RequiredArgsConstructor 어노테이션을 사용할 때는 제일 중요한 게 초기화되지 않은 final이나 @NonNull이 선언된 필드만 생성자가 생긴다는 점이에요!! 다음과 같이 사용하면 생성자가 없으므로 컴파일 오류가 발생합니다.
1) 의존성 주입을 할 때, 올바르지 않은 코드
간혹 아래 코드처럼 final을 생략한 채로 필드를 생성할 때가 있어요. 이런 경우 생성자가 없으므로 컴파일 오류가 발생하게 됩니다.
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ExampleService {
private ExampleRepository exampleRepository;
// 생성자가 없음
// 서비스 메서드들...
}
2) 의존성 주입할 때, 올바른 코드
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class ExampleService {
private final ExampleRepository exampleRepository;
// 생성자가 없어도 Lombok이 생성해줌
// 서비스 메서드들...
}
@AllArgsConstructor
AllArgsConstructor 어노테이션을 사용하면 클래스의 모든 필드를 매개변수로 받는 생성자를 자동으로 생성해 줍니다. 이를 통해 객체를 초기화할 때 모든 필드에 대한 값을 직접 설정하는 생성자를 명시적으로 작성하지 않아도 됩니다.
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@AllArgsConstructor
public class ExampleService {
private ExampleRepository exampleRepository;
// 생성자가 없어도 Lombok이 생성해줌
// 서비스 메서드들...
}