티스토리 뷰
목차
✅1. DI란 무엇인가?
"DI(Dependency Injection, 의존성 주입)"은 객체가 의존하는 객체를 직접 생성하지 않고, 외부에서 주입 받는 설계 방식이다.
- DI는 DIP(의존 역전 원칙)을 실현하는 핵심 수단이며
- Spring의 핵심 원리 중 하나인 IoC(제어의 역전)를 구체적으로 구현하는 방법이기도 하다.
2. Spring에서 DI는 어떻게 동작하는가?
Spring은 ApplicationContext가 Bean을 생성하고,
의존성 있는 객체를 자동으로 찾아서 주입한다.
Bean 등록 방식에 따라 주입 방식이 다르게 동작할 수 있다.
- 컴포넌트 스캔 기반 (@Component, @Service, @Repository)
- Java Config 기반 (@Bean 메서드)
- @Autowired나 여러 Bean이 있을 때
- 기본적으로 Spring은 ApplicationContext 초기화 시 Bean을 생성하고, @Autowired, 생성자 주입 등을 통해 타입 기반으로 의존성을 자동 주입한다.
- 주입 대상 Bean이 1개뿐이면 그대로 주입되며, 2개 이상일 경우네느 @Primary, @Qualifier 이름 매칭 등을 통해 충돌을 해결한다.
- @Primary : 우선순위를 지정해 기본 후보로 등록
- @Qualifier("beanName") : 명확히 특정 Bean을 선택
- @Autowired(required = false) -> Optional 의존성 처리
- 모든 Bean들은 컨테이너에 의해 생성되고 관리되며, 주입 시점에는 이 컨텍스트 내에서 주입 대상이 결정된다.
3. DI의 3가지 주요 방식
✅ 1) 생성자 주입 (Constructor Injection)
@Component
public class OrderService {
private final OrderRepository orderRepository;
@Autowired // 생략 가능 (Spring 4.3+)
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
📌특징
- 불변성 보장 (final 필드 가능)
- 주입 대상이 반드시 필요한 경우에 적합
- 테스트 코드에서 명시적 주입이 쉬움
- 순환 참조 문제가 발생하면 명확히 드러남
✅ 가장 권장되는 방식
✅ 2) 필드 주입 (field Injection)
@Component
public class OrderService {
@Autowired
private OrderRepository orderRepository;
}
📌특징
- 코드 간결하고 직관적
- 테스트/유닛테스트 불가능 (필드 주입은 리플렉션 없이는 변경 불가)
- 의존성이 보이지 않음 -> 클래스 간 관계가 명확하지 않음
- 스프링 컨테이너가 없으면 객체 자체가 무용지물
🚫 실무에선 지양
✅ 3) 세터 주입 (Setter Injection)
@Component
public class OrderService {
private OrderRepository orderRepository;
@Autowired
public void setOrderRepository(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
}
📌특징
- 선택적 의존성 주입 가능
- 라이브러리처럼 초기 상태 설정이 필요한 객체에 적합
- 테스트 용이성은 있으나, 주입 순서 보장 어려움
⚠️ 설정용, 보조적인 주입에 사용 권장
📊 4. 3가지 주입 방식 비교 요약
| 구분 | 생성자 주입 | 필드 주입 | 세터 주입 |
| 선언 위치 | 생성자 | 필드 | Setter 메서드 |
| 불변성 보장 | ✅ (final 사용 가능) | ❌ | ❌ |
| 테스트 용이성 | ✅ | ❌ | ✅ |
| 스프링 외 사용 | ✅ | ❌ | ✅ |
| 선택적 주입 | ❌ | ✅ | ✅ |
| 의존성 명확성 | ✅ | ❌ | 중간 |
| 실무 권장도 | ⭐ ⭐ ⭐ ⭐ | ⭐ | ⭐ ⭐ |
5. 언제 어떤 방식을 선택해야 할까?
| 상황 | 추천 방식 | 이유 |
| 필수 의존성 | 생성자 주입 | 불변성 보장, 테스트 쉬움 |
| 선택 의존성 | 세터 주입 | 필요할 때만 설정 |
| 설정/초기화 목적 | 세터 주입 | DSL이나 설정용 |
| 실습/테스트 간편 | 필드 주입 | 간단히 확인 가능 (단, 실무 지양) |
6. 실무 팁 : Lombok과 생성자 주입
@RequiredArgsConstructor
@Service
public class OrderService {
private final OrderRepository orderRepository;
}
- @RequiredArgsConstructor -> final 필드 기반 생성자 자동 생성
- @Autowired 없어도 자동 주입 (Spring 4.3+)
- 가장 현업에서 많이 사용하는 패턴
⚠️7. 순환 참조와 DI
예: 서로가 서로를 주입하면?
@Component
public class A {
public A(B b) { ... }
}
@Component
public class B {
public B(A a) { ... }
}
- 생성자 주입에서는 BeanCurrentlyInCreationException
- 해결 : 필드 주입/세터 주입 or 설계 변경 필요
✅마무리 요약
- DI는 객체 간결합을 낮추고, 테스트와 확장성을 확보하는 핵심 설계 기법
- 생성자 주입은 필수 의존성, 세터 주입은 선택적 의존성, 필드 주입은 실습용
- Spring은 DI 기반 설계를 통해 DIP, OCP 원칙을 자연스럽게 실현
- 실무에서는 Lombok + 생성자 주입 조합이 가장 많이 쓰임
🔜 다음 포스팅에서는 Spring AOP 깊이 파기 - 핵심 로직과 횡단 관심사의 완전 분리, 그리고 실무 적용 예제라는 주제로 내용을 다뤄보도록 하겠다.
'IT > Spring' 카테고리의 다른 글
| 🔄 Spring의 @Transactional 완전 정복 - 개념, 원리, 동작 방식, 트러블 슈팅까지 (0) | 2025.06.11 |
|---|---|
| Spring AOP 완전 정복 - 핵심 로직과 횡단 관심사의 완전 분리 (0) | 2025.06.05 |
| Spring Bean과 생명주기 - Bean의 생성부터 소멸까지, 내부에서는 어떤 일이 벌어질까? (0) | 2025.05.12 |
| Spring Framework의 핵심 원리 - IoC, DI, AOP (0) | 2025.05.11 |
| Spring Framework 소개 (1) | 2024.03.13 |