Spring DI와 IoC
Sample Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class HellobootApplication {
public static void main(String[] args) {
// 1. IoC 컨테이너 생성
GenericApplicationContext applicationContext = new GenericApplicationContext();
// 2. 빈 등록 - "이 클래스들을 관리해주세요!"
applicationContext.registerBean(HelloController.class);
applicationContext.registerBean(SimpleHelloService.class);
// 3. 컨테이너 초기화 - "이제 객체들을 만들어주세요!"
applicationContext.refresh();
// ... 웹서버 설정 ...
// 4. 빈 조회 - "HelloController 주세요!"
HelloController helloController = applicationContext.getBean(HelloController.class);
String ret = helloController.hello(name);
}
}
🔄 IoC (Inversion of Control) - 제어의 역전
flowchart TD
subgraph A["일반적인 방식"]
Dev1[개발자]
Dev1 --> HC1[HelloController]
Dev1 --> SHS1[SimpleHelloService]
HC1 --> SHS1
end
subgraph B["IoC 방식"]
Dev2[개발자]
Container[Spring Container]
Dev2 --> Container
Container --> HC2[HelloController]
Container --> SHS2[SimpleHelloService]
Container --> HC2
end
style A fill:#ffe6e6
style B fill:#e6ffe6
코드에서 살펴보기
일반적인 방식:
1
2
3
// 개발자가 직접 관리
HelloController controller = new HelloController();
SimpleHelloService service = new SimpleHelloService();
IoC 방식:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Spring 컨테이너가 관리
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.
registerBean(HelloController .class); // "관리해주세요"
applicationContext.
registerBean(SimpleHelloService .class); // "관리해주세요"
applicationContext.
refresh(); // "이제 만들어주세요"
// 필요할 때 요청
HelloController helloController = applicationContext.getBean(HelloController.class);
🎯 IoC의 핵심
제어권이 바뀌었습니다!
- Before: 개발자가 직접
new HelloController()
- After: 컨테이너가
applicationContext.getBean()
비유:
- 일반적인 방식: 내가 직접 요리하기
- IoC 방식: 요리사(Spring 컨테이너)에게 주문하고 받아먹기
💉 DI (Dependency Injection) - 의존성 주입
sequenceDiagram
participant Dev as 개발자
participant Container as Spring Container
participant HC as HelloController
participant SHS as SimpleHelloService
Dev->>Container: 1. 컨테이너 생성
Dev->>Container: 2. 빈 클래스 등록
Container->>Container: 3. 의존성 분석
Container->>SHS: 4. SimpleHelloService 생성
Container->>HC: 5. HelloController 생성
Container->>HC: 6. SimpleHelloService 주입
Dev->>Container: 7. getBean 요청
Container->>Dev: 8. HelloController 반환
HelloController는 SimpleHelloService가 필요해요!
1
2
3
4
5
6
7
public class HelloController {
// SimpleHelloService가 필요합니다! (의존성)
public String hello(String name) {
// 여기서 SimpleHelloService를 어떻게 얻을까요?
return "Hello " + name;
}
}
문제: HelloController가 SimpleHelloService를 어떻게 얻을까요?
❌ 잘못된 방법: 직접 생성
1
2
3
4
5
6
7
public class HelloController {
public String hello(String name) {
// 매번 새로 생성 - 메모리 낭비!
SimpleHelloService service = new SimpleHelloService();
return service.sayHello(name);
}
}
문제점:
- 매번 새 객체 생성 (메모리 낭비)
- HelloController가 SimpleHelloService에 강하게 결합
- 테스트하기 어려움
- 다른 서비스로 바꾸기 어려움
🎯 DI의 세 가지 방법
flowchart TD
subgraph Constructor["생성자 주입 - 추천"]
C1[HelloController 생성시]
C2[파라미터로 주입]
C3[final 필드 설정]
C1 --> C2 --> C3
end
subgraph Field["필드 주입"]
F1[HelloController 생성]
F2[@Autowired 애노테이션]
F3[필드 직접 설정]
F1 --> F2 --> F3
end
subgraph Setter["세터 주입"]
S1[HelloController 생성]
S2[세터 메서드 호출]
S3[필드 설정]
S1 --> S2 --> S3
end
1. 생성자 주입 (Constructor Injection) ⭐ 추천!
1
2
3
4
5
6
7
8
9
10
11
12
public class HelloController {
private final SimpleHelloService helloService; // final 사용 가능!
// 생성자를 통해 의존성 주입받기
public HelloController(SimpleHelloService helloService) {
this.helloService = helloService;
}
public String hello(String name) {
return helloService.sayHello(name); // 주입받은 서비스 사용
}
}
Spring 컨테이너가 하는 일:
1
2
3
4
5
6
7
8
// 1. SimpleHelloService 생성
SimpleHelloService service = new SimpleHelloService();
// 2. HelloController 생성하면서 서비스 주입
HelloController controller = new HelloController(service);
// 3. 컨테이너에 저장
applicationContext에 저장
장점:
- ✅ 불변성 보장:
final
키워드 사용 가능 - ✅ 필수 의존성 명시: 생성자 파라미터로 명확히 표현
- ✅ 테스트 용이:
new HelloController(mockService)
가능 - ✅ 순환 참조 방지: 컴파일 시점에서 발견
2. 필드 주입 (Field Injection) ⚠️ 주의
1
2
3
4
5
6
7
8
public class HelloController {
@Autowired
private SimpleHelloService helloService; // 필드에 직접 주입
public String hello(String name) {
return helloService.sayHello(name);
}
}
Spring 컨테이너가 하는 일:
1
2
3
4
5
// 1. HelloController 생성 (빈 생성자로)
HelloController controller = new HelloController();
// 2. Reflection으로 필드에 주입
// controller.helloService = applicationContext.getBean(SimpleHelloService.class);
장점:
- ✅ 코드가 간결함
단점:
- ❌
final
사용 불가 (불변성 보장 안됨) - ❌ 테스트하기 어려움 (Reflection 필요)
- ❌ 의존성이 숨겨짐 (얼마나 많은 의존성이 있는지 모름)
3. 세터 주입 (Setter Injection) 🤔 선택적 사용
1
2
3
4
5
6
7
8
9
10
11
12
public class HelloController {
private SimpleHelloService helloService;
@Autowired
public void setHelloService(SimpleHelloService helloService) {
this.helloService = helloService;
}
public String hello(String name) {
return helloService.sayHello(name);
}
}
Spring 컨테이너가 하는 일:
1
2
3
4
5
6
7
// 1. HelloController 생성
HelloController controller = new HelloController();
// 2. setter 메서드 호출하여 주입
controller.setHelloService(
applicationContext.getBean(SimpleHelloService.class)
);
장점:
- ✅ 선택적 의존성: 꼭 필요하지 않은 의존성 표현 가능
- ✅ 런타임 변경: 실행 중에 의존성 교체 가능
단점:
- ❌
final
사용 불가 - ❌ 의존성 누락 위험 (setter를 호출하지 않을 수 있음)
🏭 Spring 컨테이너의 생명주기
Container 내부 구조
graph TB
subgraph Container["Spring Container"]
Registry[Bean Registry]
Factory[Bean Factory]
Injector[Dependency Injector]
end
Registry --> Factory
Factory --> Injector
subgraph Beans["생성된 빈들"]
HC[HelloController]
SHS[SimpleHelloService]
end
Injector --> Beans
HC -.-> SHS
flowchart LR
A[컨테이너 생성] --> B[빈 등록]
B --> C[초기화 refresh]
C --> D[빈 조회 getBean]
C --> E[의존성 분석]
E --> F[객체 생성]
F --> G[의존성 주입]
코드에서 일어나는 일을 단계별로 살펴보세요:
1단계: 컨테이너 생성
1
GenericApplicationContext applicationContext = new GenericApplicationContext();
컨테이너: “안녕하세요! 객체 관리 서비스 시작합니다!”
2단계: 빈 등록
1
2
applicationContext.registerBean(HelloController .class);
applicationContext.registerBean(SimpleHelloService .class);
컨테이너: “HelloController와 SimpleHelloService를 등록했습니다!”
3단계: 의존성 분석 및 객체 생성
1
applicationContext.refresh();
컨테이너의 분석:
1
2
3
"음... HelloController 생성자를 보니 SimpleHelloService가 필요하네요.
1. 먼저 SimpleHelloService를 만들고
2. 그다음 HelloController를 만들면서 SimpleHelloService를 주입해주겠습니다!"
4단계: 빈 조회
1
HelloController helloController = applicationContext.getBean(HelloController.class);
컨테이너: “이미 만들어놓은 HelloController를 드립니다!”
🎯 핵심 정리
IoC (제어의 역전)
1
2
3
4
5
// 여러분의 코드에서
GenericApplicationContext applicationContext =
new GenericApplicationContext();
// 컨테이너가 관리!
pplicationContext.registerBean(HelloController.class);
제어권이 개발자 → Spring 컨테이너로 이동
DI (의존성 주입)
필요한 의존성을 외부에서 넣어줌
1
2
3
4
// HelloController가 SimpleHelloService를 주입받음
public HelloController(SimpleHelloService helloService) {
this.helloService = helloService; // 외부에서 주입받음!
}
댓글남기기