의존성 주입(Dependency Injection)은 객체 간의 의존성을 외부에서 주입받아 결합도를 낮추고 코드의 유연성과 테스트 가능성을 높이는 소프트웨어 설계 패턴이다, 스프링에서 사용하는 의존성 주입 방식에는 필드 주입, 생성자 주입, Setter 주입 세 가지를 정리해보려 한다.
필드주입(Field Injection)
클래스의 필드에 @Autowired를 직접 사용하여 의존성을 주입한다.
장점
- 간결한 코드로 구현가능.
- 생성자나 Setter 없이 의존성을 선언 가능
단점
- private 필드 접근의 제한으로 *테스트가 어렵다.
- 객체초기화 시점에 의존성을 강제하지 않아 null point exception 발생하여 예외가 발생할 있다.
- 클래스의 의존성을 명시적으로 표현하지 않아 코드 가독성이 떨어진다.
*필드주입의 테스트가 어려운 이유
클래스의 생성자나 메서드 시그니처에 의존성이 드러나지 않으므로 어떤 의존성이 필요한지 파악하기 힘들고 의존성을 명시적으로 주입할 수 없다.
예시)
@SpringBootTest
class MyServiceTest {
@Autowired
private MyService myService; // 필드 주입
}
MyService에 어떤 의존성이 주입되는지 명확히 확인하기 어렵습니다. 생성자나 세터 주입 방식으로 했다면, @Autowired와 함께 실제 어떤 값이 주입될지 더 명확하게 제어할 수 있습니다.
생성자 주입(Constructior Injection)
의존성을 생성자를 통해 주입받는다. 스프링에서는 생성자가 하나만 있을 경우 @Autowired를 생략가능
장점
- 필드를 final로 선언할 수 있어 불변성을 보장
- 클래스의 의존성이 명확히 드러난다
- 테스트에서 의존성을 쉽게 Mock로 교체 가능
*실제 객체를 만들기엔 비용과 시간이 많이 들거나 의존성이 길게 걸쳐져 있어 제대로 구현하기 어려울 경우, 가짜 객체를 만들어 사용하는데 이것을 Mock이라 한다. - 런타임에서 *순환참조를 감지해 오류를 방지
단점
- 의존성이 많아질 경우 생성자 매개변수가 많아질 수 있다.
*순환참조
두 개 이상의 빈(Bean)이 서로를 참조하는 상황으로 인해 스프링이 빈을 초기화하기 못하는 문제를 말한다.
순환참조 예시
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AService {
private final BService bService;
@Autowired
public AService(BService bService) {
this.bService = bService;
}
public void print() {
System.out.println("AService");
}
}
@Service
public class BService {
private final AService aService;
@Autowired
public BService(AService aService) {
this.aService = aService;
}
public void print() {
System.out.println("BService");
}
}
설명
- AService 클래스는 BService를 의존하고 있습니다.
- BService 클래스는 AService를 의존하고 있습니다.
이렇게 되면 Spring은 AService와 BService를 생성하려고 할 때, 각각의 객체를 생성하는 과정에서 서로가 서로를 참조하므로 무한 루프에 빠지게 됩니다.
순환참조의 해결방안
- @Lazy 어노테이션 사용하면 빈 초기화를 지연시켜 순환참조 문제를 해결할 수 있다.
- 순환 참조의 한쪽을 Setter 주입(Setter Inject) 방식으로 변경하여 해결
- 순환 참조 자체를 제거하는 설계변경 가장 바람직하다.
*스프링 부트 2.6이상에서는 순환참조금지가 기본설정으로 순환참조를 허용하려면 설정을 변경해야 한다.
Setter 주입(Setter Injection)
Setter 메서드에 @Autowired를 사용하여 의존성을 주입한다.
장점
- 의존성을 선택적으로 설정가능
- 유연한 초기화가 필요한 경우 유용
단점
- 필수 의존성이 누락될 가능성이 있어 객체의 *불완전 상태가 발생할 수 있다.
- Setter가 외부에서 호출되어 의존성이 변경될 위험이 있다.
*Setter 주입의 불완전상태 발생이유
- 객체 생성 이후 스프링 컨테이너가 setter 메서드를 호출할 때 의존성이 주입된다. 주입이 완료되기 전 객체가 사용되면 의존성이 없는 상태로 동작할 수 있다.
- 주입순서를 명시적으로 제어하기 어렵고 이로 인해 객체가 완전한 상태로 초기화되지 않을 가능성이 있다.
- 주입 대상 필드가 초기화 전에 호출되면 null point exception이 발생할 수 있다.
[결과]
필드주입은 간단한 구현에 사용하지만 테스트가 private접근제한이나 객체 초기화 시점의 의존성 강제하지 않아 테스트가 어려울 수 있다.
생성자 주입은 final로 선언이 가능해 불변성이 보장되고 클래스의 의존성이 명확히 드러난다. 하지만 의존성이 많아지면 생성자 매개변수가 많아질 수 있다.
Setter 주입은 유연한 초기화, 의존성을 선택적으로 설정 가능하나 필수 의존성이 누락될 가능성이 있어 객체의 불완전한 상태가 발생할 수 있다.