IoC

객체의 생성·생명주기·의존성 관리를 개발자가 아닌 외부 컨테이너가 책임지도록 뒤집는 설계 원칙입니다 (Inversion of Control). DI보다 큰 개념으로, DI는 IoC를 구현하는 방법 중 하나입니다.

왜 필요한가?

[ 전통적인 제어 흐름 ]
public class UserService {
    private EmailService emailService = new EmailService();
                                          ↑
                                  내가(코드 작성자가)
                                  객체를 만든다
}
이 방식의 문제:
- 어떤 EmailService를 쓸지 결정 권한 = UserService에 있음
- 다른 메일 서비스로 교체하려면 UserService 코드 변경
- 객체 생성 시점·횟수도 UserService가 통제

→ 모든 책임이 한 클래스에 몰림
[ IoC가 적용된 흐름 ]
public class UserService {
    private final EmailService emailService;

    public UserService(EmailService emailService) {
        this.emailService = emailService;     ← 외부에서 받은 걸 사용
    }
}
권한 이동:
- "어떤 EmailService를 쓸지" 결정 = 외부(컨테이너/설정)
- "언제 만들지" 결정 = 외부
- "몇 개를 만들지" 결정 = 외부

→ 제어권이 개발자(코드)에서 외부로 "역전"됨

헐리우드 원칙

"Don't call us, we'll call you"
"우리한테 전화하지 마, 우리가 너한테 전화할게"

→ 객체가 의존성을 능동적으로 가져오는(call) 게 아니라
   컨테이너가 객체에게 의존성을 던져주는(callback) 형태
[ 제어 흐름 비교 ]

전통:
  내 코드 → "필요해" → new EmailService() 호출 → 사용

IoC:
  컨테이너 → "여기 있어" → 내 코드에 의존성 전달 → 내 코드 사용

IoC를 구현하는 방법

방법설명
DI (의존성 주입)외부에서 의존성을 생성자/필드/세터로 주입 — 가장 흔함
Service Locator중앙 레지스트리에서 의존성을 조회 (안티패턴 취급)
템플릿 메서드추상 클래스가 흐름을 제어, 하위가 일부 단계만 구현
이벤트 기반이벤트 디스패처가 콜백 호출 시점 결정
// Service Locator 예시 (참고용 — 안티패턴)
public class UserService {
    private EmailService email = ServiceLocator.get(EmailService.class);
    //                              ↑
    //                       객체가 능동적으로 가져옴 — IoC지만 DI는 아님
}

// DI 방식 (권장)
public class UserService {
    private final EmailService email;
    public UserService(EmailService email) {
        this.email = email;        // 수동적으로 받음
    }
}

Service Locator는 IoC지만 의존성이 코드에 숨어서 테스트가 어려움. DI가 더 명시적.

Spring의 IoC 컨테이너

Spring = 자바 진영의 대표적인 IoC 컨테이너 프레임워크

기능:
1. 빈(Bean)의 생성
2. 의존성 주입
3. 생명주기 관리 (초기화, 소멸)
4. 싱글톤 등 스코프 관리
[ Spring이 IoC를 실현하는 흐름 ]

1. @Component 등이 붙은 클래스 스캔
2. 컨테이너가 객체 생성 (개발자 코드는 new 안 함)
3. 의존성 분석 → 자동 주입
4. 빈으로 등록 → 필요할 때 꺼내 씀
5. 종료 시 정리 (@PreDestroy 호출 등)

개발자는 "무엇이 필요한지"만 선언, "어떻게 얻을지"는 컨테이너 책임

IoC와 DI의 관계

        IoC (큰 개념)
         └─ "제어권을 외부로 넘긴다"
            │
            ├─ DI (구체 구현)
            │   └─ "의존성을 주입받는다"
            │
            ├─ Service Locator
            │   └─ "레지스트리에서 조회한다"
            │
            └─ 다른 방법들...
항목IoCDI
범위넓음 (원칙)좁음 (패턴)
초점"누가 통제하나""어떻게 의존성을 주입하나"
예시Spring 전체 철학@Autowired, 생성자 주입

"스프링은 IoC 컨테이너이며, 그 핵심 메커니즘으로 DI를 사용한다"가 정확한 표현.

IoC가 만들어내는 효과

[ 결합도 감소 ]
구체 구현이 아니라 인터페이스에 의존
→ 구현체 교체가 자유로움

[ 테스트 용이 ]
의존성을 외부에서 주입할 수 있으니 Mock 객체 주입 가능

[ 횡단 관심사 분리 ]
객체 생성, 트랜잭션, 로깅 등을 컨테이너가 처리
→ 비즈니스 로직만 코드에 남음 (Spring AOP와 결합)

[ 단일 책임 ]
객체 생성 책임을 떠나 본연의 역할에만 집중 (SOLID SRP)

자주 하는 오해

X "IoC = DI"
→ DI는 IoC의 구현 방식 중 하나일 뿐

X "Spring 안 쓰면 IoC 불가능"
→ IoC는 설계 원칙. 직접 구현해도 됨 (수동 DI도 IoC)

X "IoC를 쓰면 무조건 좋다"
→ 작은 프로젝트에서는 컨테이너 학습 비용이 더 클 수도

핵심 정리

IoC는 객체의 생성·생명주기 통제권을 개발자에서 외부 컨테이너로 넘기는 설계 원칙으로, "Don't call us, we'll call you"라는 헐리우드 원칙으로 표현됩니다. 가장 흔한 구현 방식이 DI이며, Spring은 IoC 컨테이너로서 빈 생성부터 의존성 주입, 생명주기 관리까지 자동화합니다. IoC를 통해 결합도가 낮아지고 테스트가 쉬워지며, SOLID 원칙(특히 DIP)을 자연스럽게 따르는 구조가 됩니다.

CSLOG에서 보기