의존성 주입(Dependency Injection)이란?
- 객체 간의 의존 관계를 외부에서 설정해주는 설계 방식.
- 객체 A가 작업 수행을 위해 객체 B가 필요 할 때, B를 A 내부에서 생성하는 것이 아니라 외부(C)에서 생성한 후 A에 주입한다.
- 이를 통해 A와 B 간의 결합도를 낮춰 유연하고 테스트 가능한 구조를 만든다.
의존성 구성 요소
| 구성 요소 |
설명 |
| 의존성 (Dependency) |
A가 사용하려는 객체 = B (예: OrderRepository) |
| 의존성 소비자 (Dependent) |
의존성을 사용하는 객체 = A(예: OrderService) |
| 주입자 (Injector) |
의존성을 생성하고 소비자에게 주입하는 외부 객체 =C (예: Spring 컨테이너) |
의존성 주입이 필요한 이유
- 변경에 유연한 구조를 만들기 위함 (OCP 원칙 - 확장에는 열려 있고 변경에는 닫혀야 함)
- 테스트 가능성 향상 (Mock 객체 주입 등으로 단위 테스트 용이)
- 재사용성과 유지보수성 향상 (객체 생성 책임을 분리)
주입 방식
1. 생성자 주입 (Constructor Injection)
- 존재 이유
- 필수 의존성을 반드시 설정하도록 강제하기 위함
- 객체가 항상 유효한 상태로 생성되도록 보장
- 사용되는 이유
- 불변성(immutable)이 중요한 객체 설계에 적합
- 객체 생성 시점에 모든 의존성이 준비되어야 할 때
- 필수 의존성을 빠뜨리는 실수를 컴파일 시점에 방지할 수 있음
예제와 구성요소 설명
// 의존성 소비자 (Dependent)
public class OrderService {
private final OrderRepository orderRepository; // 의존성 1
private final NotificationService notificationService; // 의존성 2
// 주입자(Injector)가 두 의존성을 생성자에 주입
public OrderService(OrderRepository orderRepository,
NotificationService notificationService) {
this.orderRepository = orderRepository;
this.notificationService = notificationService;
}
public void placeOrder(Long userId, String item) {
orderRepository.saveOrder(userId, item);
notificationService.send("Order placed for item: " + item);
}
}
- 의존성 (Dependency): OrderRepository, NotificationService
- 의존성 소비자 (Dependent): OrderService
- 주입자 (Injector): 스프링 컨테이너가 생성자 호출 시 의존성 주입
2. Setter 주입 (Setter Injection)
- 존재 이유
- 선택적 의존성을 주입할 수 있도록 하기 위함
- 필요에 따라 일부 의존성만 설정할 수 있도록 유연한 구조 제공
- 사용되는 이유
- 초기화 이후 변경이 가능해야 할 때
- 객체가 반드시 특정 의존성을 필요로 하진 않는 경우
- 테스트 시에 특정 의존성만 따로 설정할 수 있도록 하기 위함
예제와 구성요소 설명
// 의존성 소비자 (Dependent)
public class OrderService {
private OrderRepository orderRepository; // 의존성 1
private NotificationService notificationService; // 의존성 2
// 기본 생성자
public OrderService() {}
// 주입자(Injector)가 setter 메서드를 통해 각각 주입
public void setOrderRepository(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public void setNotificationService(NotificationService notificationService) {
this.notificationService = notificationService;
}
public void placeOrder(Long userId, String item) {
orderRepository.saveOrder(userId, item);
notificationService.send("Order placed for item: " + item);
}
}
- 의존성: OrderRepository, NotificationService
- 소비자: OrderService
- 주입자: 스프링 컨테이너 또는 테스트 코드에서 setter 호출
3. 메서드 주입 (Method Injection)
- 존재 이유
- 일시적, 동적인 의존성을 주입하기 위함
- 메서드 실행 시점마다 다른 의존성이 필요할 수 있음
- 사용되는 이유
- 요청마다 다른 Formatter, 전략(Strategy) 등을 주입해야 할 때
- 상태를 갖지 않는, 단발성 의존성 주입에 적합
예제와 구성요소 설명
public class ReportService {
public void generateReport(ReportData data, ReportFormatter formatter) {
formatter.format(data);
}
}
- 의존성: ReportFormatter
- 소비자: ReportService
- 주입자: 메서드를 호출하는 클라이언트 코드 (직접 인자로 전달)