들어가며: "객체를 누가 생성하고, 누가 연결하며, 누가 관리해야 하는가?"
앞 장에서는 객체가 자신의 의존성을 직접 new로 생성하는 구조가 왜 단일 책임 원칙(SRP)과 개방-폐쇄 원칙(OCP)을 뒤흔드는지 살펴보았다. 객체가 자신의 비즈니스 로직뿐만 아니라 "타 객체의 생성 및 조립"이라는 무거운 책임까지 끌어안게 되면, 코드는 구체 클래스와 단단히 결합되어 작은 변화에도 쉽게 무너지는 취약한 구조로 전락한다. 그렇다면 객체지향의 이상을 실현하기 위해 객체 생성과 관리라는 거대한 책임은 대체 누가 가져야 할까? Spring이 제시하는 답은 단호하고 명쾌하다.
"객체가 스스로를 제어하지 말고, 제3의 존재인 컨테이너(Container)가 객체를 제어하도록 만들자."
이 수동적인 변화이자 거대한 거버넌스의 이동을 제어의 역전(IoC, Inversion of Control)이라고 부른다. 이번 장에서는 IoC를 단순한 오해—"객체를 대신 생성해 주는 자바 라이브러리"—에서 탈피시켜, 프로그램의 주도권이 애플리케이션에서 프레임워크로 넘어가는 거대한 설계 철학의 관점으로 재정립해 본다.
3.1 제어(Control)란 무엇인가?
IoC라는 단어를 완벽히 내면화하려면, 먼저 소프트웨어 맥락에서 제어(Control)가 지니는 정확한 의미와 권한의 범위를 정의해야 한다. 개발 현장에서 '제어'는 단순히 특정 객체의 메서드를 호출하는 실행 행위만을 뜻하지 않는다. 객체지향 애플리케이션에서 제어권이란 다음 5가지 핵심 권한을 포괄한다.
- 객체 생성 권한: 어떤 클래스를 바탕으로 언제 인스턴스를 메모리에 올릴지 결정한다.
- 의존성 연결 권한: 인스턴스화된 객체들 사이에 어떤 참조 관계를 맺어줄지 결정한다.
- 생명주기(Lifecycle) 관리 권한: 객체가 언제 초기화되고, 언제 파괴(소멸)될지 관리한다.
- 구현체 선택 권한: 인터페이스 타입 뒤에 실제로 동작할 구체 클래스를 지정한다.
- 객체 제공 권한: 필요한 시점에 애플리케이션의 특정 지점으로 객체를 공급한다.
즉, 제어란 애플리케이션이라는 거대한 기계의 '객체 그래프'를 설계하고, 조립하며, 런타임에 운영하는 총괄 통제권을 의미한다.
3.2 기존 애플리케이션에서는 누가 제어하는가?
Spring의 도움을 받지 않는 전통적인 자바 애플리케이션에서는 개별 객체들이 능동적인 주체로서 제어권을 행사한다. 예를 들어, OrderService는 자신이 일할 때 필요한 PaymentService를 직접 생성하고, PaymentService는 또 다시 자신이 사용할 PaymentGateway를 직접 인스턴스화한다.

이 구조에서는 제어권이 애플리케이션 곳곳에 산발적으로 파편화되어 있다.
모든 객체가 자율적으로 타 객체를 만들어 내다 보니, 겉보기에는 능동적이고 자유로워 보인다. 그러나 특정 객체의 생성 방식이 변경되거나 새로운 구체 클래스가 들어오는 순간, 그 객체를 생성하던 모든 클라이언트 코드에 연쇄적인 수정의 파도가 덮친다. 능동적인 제어가 역설적으로 "수정의 불확실성을 폭증시키는 부작용"을 낳는 것이다.
3.3 제어가 역전된다는 것은 무엇인가?
IoC가 적용된 Spring 환경에서는 객체가 더 이상 수동적인 생성자(`new`)를 직접 호출하지 않는다.
애플리케이션이 로딩되는 시점에 Spring Container라는 제3의 주체가 최상위에서 먼저 깨어나 전체 시스템의 주도권을 쥔다.

이제 OrderService는 PaymentService를 어떻게 만들지 고민하지 않는다. 자신이 인스턴스를 직접 복제해 올 권한을 완전히 내려놓고, 단지 "나는 PaymentService 역할이 필요하다"고 정중하게 선언할 뿐이다.
public class OrderService {
private final PaymentService paymentService;
// 객체 생성의 책임은 철저히 외부에 위임한다.
// 그저 전달받은 인스턴스를 감사히 받아 사용할 뿐이다.
public void OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
OrderService는 객체 생성 프로세스에서 완전히 배제되었다. 이미 컨테이너가 생성하고 검증을 끝낸 PaymentService 구현체를 넘겨받아 '사용'하는 일에만 집중한다. 이처럼 객체의 생성, 조립, 관리에 대한 능동적인 제어권이 애플리케이션 코드에서 외부의 컨테이너로 이동한 현상, 이것이 바로 IoC의 실체다.
3.4 왜 '역전(Inversion)'이라고 부르는가?
많은 개발자가 이 대목에서 소박한 의구심을 가진다. "결국 프레임워크가 객체를 대신 만들어 준다는 뜻 아닌가요? 그런데 왜 하필 '역전'이라는 거창하고 거친 표현을 쓸까요?" 그 이유는 프로그램을 이끌어가는 주도권의 흐름(Flow of Control)이 180도 뒤집혔기 때문이다.
1) 라이브러리(Library)를 사용할 때: 애플리케이션이 주도
전통적인 개발 방식에서 애플리케이션 코드는 주체(Master)이고, 라이브러리는 도구(Slave)다.

개발자가 작성한 main() 메서드나 서비스 로직이 전체 흐름을 지배하며, 필요한 순간에 라이브러리의 함수를 가져다 쓰고 다시 제어권을 회수한다.
2) 프레임워크(Framework)를 사용할 때: 프레임워크가 주도
하지만 Spring 같은 프레임워크의 세계에서는 관계가 완벽히 거꾸로 바뀐다.

프레임워크가 먼저 실행되어 전체 애플리케이션의 뼈대를 세우고, 객체 생명주기를 관장하며, 특정 시점이 되면 개발자가 만들어 둔 코드(`@Controller`, `@Service` 등)를 알아서 호출(Callback)해 준다.
- 과거: 내가 작성한 코드가 프레임워크/라이브러리를 능동적으로 호출한다.
- 현재: 프레임워크가 내가 작성한 코드를 수동적으로 호출해 준다.
주객(主客)이 완전히 뒤바뀌었기 때문에 이를 제어의 역전(Inversion of Control)이라는 명징한 용어로 명명한 것이다.
3.5 IoC는 단순 객체 생성을 초월한다
IoC를 그저 "객체를 대신 인스턴스화해 주는 인스턴스 생성기" 정도로 치부하면 Spring의 수많은 정수를 놓치게 된다.
Spring Container가 제어권을 거머쥠으로써 애플리케이션에 일어나는 혁신적인 일들은 다음과 같다.
- Bean Lifecycle 통제: 객체의 생성부터 `@PostConstruct` 초기화, 그리고 애플리케이션 종료 시 `@PreDestroy` 소멸까지 생명주기를 정교하게 제어한다.
- Bean Scope 관리: 객체를 싱글톤(Singleton)으로 유지할지, 요청마다 새로 만들지(Prototype, Request) 제어한다.
- AOP(관점 지향 프로그래밍) 프록시 제어: 트랜잭션(`@Transactional`), 보안, 로깅 코드를 원본 객체 생성 시점에 프록시(Proxy)로 감싸서 유연하게 동적 주입한다.
- 이벤트 기반 생태계 구축: ApplicationEventPublisher를 통해 객체 간의 결합도를 0에 가깝게 낮추는 이벤트 발행 및 수신 환경을 제공한다.
이처럼 IoC는 객체의 생성이라는 단편적 기능을 넘어, 애플리케이션 전체의 실행 환경(Runtime Environment)을 컨테이너가 총괄 운영하는 거대한 아키텍처 패러다임이다.
3.6 IoC와 DI의 관계: 목적과 수단
스프링을 처음 배울 때 IoC와 DI라는 용어가 혼용되어 혼란을 느끼기 쉽다. 그러나 두 개념은 명확히 구분된다. 목적과 수단의 관계로 정리하면 명쾌하다.

- IoC (Inversion of Control): "객체에 대한 제어권을 외부 컨테이너로 넘기자"라는 상위 수준의 설계 철학이자 목표(Goal)
- DI (Dependency Injection): IoC라는 철학을 자바 언어 상에서 실제로 구현해 내는 구체적인 기술이자 수단(Mechanism)
OrderService가 생성자를 통해 PaymentService를 파라미터로 전달받는 행위 자체는 DI(의존성 주입)다. 그리고 그 PaymentService에 어떤 구체 구현 객체(TossPaymentService)를 채워 넣어서 생성자를 호출할지 전체 판을 짜고 제어하는 주체가 IoC Container다.
3.7 Spring은 객체지향을 어떻게 완성했는가?
객체지향 설계의 핵심 명제는 "역할(인터페이스)과 구현(클래스)을 분리하여, 객체가 추상화에만 의존하게 만드는 것"이었다.
그러나 개발자가 new 구체클래스()를 적어 내리는 순간, 이 원칙은 허망하게 붕괴했다.
Spring은 IoC라는 철학을 도입하여 객체 생성의 책임을 애플리케이션에서 완벽하게 들어냈다. 그 결과 비즈니스 객체들은 비로소 "100% 순수한 추상화(인터페이스)"에만 의존할 수 있는 기적을 맛보게 되었다.
Spring이 선사한 것은 단순한 자동 생성 도구가 아니다. 개발자가 SOLID 원칙을 흔들림 없이 고수하며 순수한 객체지향 설계를 마음껏 펼칠 수 있도록 든든하게 받쳐주는 거대한 판(Runtime Environment)인 것이다.
Chapter 3 핵심 정리
- 제어(Control)의 정의: 객체 생성, 의존성 연결, 생명주기 관리, 프록시 적용 등 애플리케이션 전체 객체 그래프를 운영하는 통합 권한을 뜻한다.
- 제어의 역전(IoC): 객체의 생명주기 제어권을 애플리케이션 코드에서 제3의 주체인 컨테이너로 완전히 넘기는 설계 철학이다.
- '역전'의 진정한 의미: 내가 프레임워크를 호출하던 구조에서, 프레임워크가 나의 코드를 호출하는 구조로 프로그램의 주도권이 뒤바뀌었음을 의미한다.
- IoC와 DI의 관계: IoC가 "무엇을 이룰 것인가(목표/철학)"라면, DI는 "어떻게 이룰 것인가(구체적 메커니즘)"다.
- 객체지향의 완성: IoC를 통해 객체가 생성 책임을 내려놓음으로써, 비로소 pure한 인터페이스 기반 느슨한 결합이 완성된다.
'🍃SpringBoot' 카테고리의 다른 글
| Spring IoC & DI 완전 정복: Chapter 5. BeanDefinition과 Bean 생성 과정 (0) | 2026.07.22 |
|---|---|
| Spring IoC & DI 완전 정복: Chapter 4. Spring Container는 무엇인가? (0) | 2026.07.22 |
| Spring IoC & DI 완전 정복: Chapter 2: "객체는 왜 스스로 필요한 객체를 만들면 안 될까?" (1) | 2026.07.22 |
| Spring IoC & DI 완전 정복: Chapter 1: "객체지향은 왜 등장했을까?" (0) | 2026.07.22 |
| JPA [2/4] : 엔티티 매핑과 연관관계 (0) | 2026.06.23 |
