- construction-injectiondependency-injectionjavalombokspring-autowiredspring-framework
Spring Framework에서 생성자 주입을 권장하는 이유
과거에는 필드에 바로 @Autowired를 붙이는 필드 주입(Field Injection) 방식이 많이 사용되었다. 하지만 최근에는 생성자 주입(Constructor Injection)이 Spring에서 권장되는 표준 방식이다. 그 이유는 단순한 코드 스타일 문제가 아니라 객체지향 설계의 안정성과 테스트 용이성 때문이다.
필드 주입의 문제점 (@Autowired)
아래 코드는 간결해 보이지만 여러 가지 구조적인 문제를 가진다.
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // final 사용 불가
public void join() {
userRepository.save();
}
}불변성(Immutability) 보장 불가
필드 주입은 객체가 생성된 이후에 의존성을 주입한다. 따라서 필드를 final로 선언할 수 없다. 이로 인해 객체가 생성된 이후에도 의존 객체가 변경될 가능성이 생기며, 객체의 상태 안정성을 보장하기 어렵다.
순환 참조 문제 발견이 늦어질 수 있음
두 클래스가 서로를 참조하는 순환 참조(Circular Dependency) 상황이 발생할 수 있다. 필드 주입이나 수정자 주입에서는 애플리케이션이 실행되어 실제 로직이 호출되는 시점에 StackOverflowError가 발생하며 서버가 다운될 수 있다. 반면 생성자 주입은 애플리케이션 컨텍스트가 로딩되는 시점에 순환 참조를 감지하여 예외를 발생시킨다. 따라서 문제를 배포 전 단계에서 빠르게 발견하고 수정할 수 있다.
테스트 코드 작성의 어려움
필드 주입은 Spring의 의존성 주입 기능에 강하게 의존한다. 따라서 Spring 컨테이너 없이 수행하는 순수한 자바 단위 테스트에서 의존 객체를 주입하기가 매우 어렵다. 결국 리플렉션을 사용해 강제로 값을 넣거나 Spring 환경을 직접 띄워야 한다. 이는 테스트 속도를 느리게 만들고 테스트 코드 작성의 부담을 증가시킨다.
단일 책임 원칙(SRP) 위반 가능성
필드 주입은 단순히 필드에 @Autowired만 추가하면 의존성을 쉽게 늘릴 수 있다. 이로 인해 하나의 클래스가 지나치게 많은 의존성을 가지게 될 위험이 있으며 결과적으로 단일 책임 원칙(SRP)을 위반하는 설계로 이어질 수 있다.
생성자 주입의 장점
객체 생성 시점에 의존성이 완성됨 (불변성 보장)
생성자 주입을 사용하면 객체가 생성되는 시점에 모든 의존성이 주입된다. 특히 final 키워드를 사용하면 의존 객체가 한 번 주입된 이후에는 외부에서 변경될 수 없다. 이러한 구조는 객체의 상태를 안정적으로 유지하게 해 준다. 또한 호출 시점에 의존성 누락으로 인한 NullPointerException을 방지할 수 있어 런타임 안정성이 높아진다.
순환 참조를 애플리케이션 시작 시점에 발견
생성자 주입을 사용하면 빈을 생성하는 시점에 의존 관계를 파악한다. 만약 A가 B를 필요로 하고, B가 다시 A를 필요로 하는 순환 구조라면 애플리케이션 시작 단계에서 BeanCurrentlyInCreationException이 발생하며 구동이 중단된다. 이를 통해 런타임 에러를 방지하고 조기에 설계를 교정할 수 있다.
순수한 자바 테스트 가능 (POJO 기반 테스트)
생성자 주입을 사용하면 Spring 컨테이너를 띄우지 않고도 테스트 코드를 작성할 수 있다. 필드 주입 방식에서는 @Autowired가 동작해야 하므로 보통 Spring 테스트 환경이 필요하다. 하지만 생성자 주입을 사용하면 개발자가 직접 객체를 생성하고 가짜 객체(Mock)를 주입할 수 있기 때문이다. 이러한 단위 테스트는 컨테이너 로딩 시간이 없으므로 실행 속도가 매우 빠르며, 코드의 독립성을 보장한다.
// 스프링 없이 순수 자바 테스트 가능
OrderService fakeService = new FakeOrderService();
OrderController controller = new OrderController(fakeService);그 외의 장점들
- 컴파일 타임 체크: final 필드를 생성자에서 초기화하지 않으면 컴파일 에러가 발생하여 개발자의 실수를 방지한다.
- 프레임워크 비의존성: 의존성 주입을 자바 표준 방식을 통해 처리하므로 Spring 프레임워크가 없는 환경에서도 해당 클래스를 재사용할 수 있다.
- Lombok과의 조합: @RequiredArgsConstructor 어노테이션을 사용하면 final 필드에 대한 생성자를 자동으로 생성해주어 코드를 매우 간결하게 유지할 수 있다.
Spring에서는 객체의 안정성과 테스트 용이성을 위해 생성자 주입(Constructor Injection) 방식을 권장한다. 특히 final 필드와 Lombok의 @RequiredArgsConstructor를 함께 사용하면 코드의 안정성과 가독성을 동시에 확보할 수 있다.
(끝)