공부메모 & 오류해결/Spring Boot

[Spring] 제어의 역전(Inversion of Control, IoC) 이란 무엇일까?

남건욱 2025. 11. 5. 19:54

목차

    반응형

     

    1. DI와 IoC

    https://ngwdeveloper.tistory.com/205

     

    [Spring] 의존성 주입(Dependency injection, DI) 이란 무엇일까?

    spring을 사용해 개발을 하다보면 @Autowired나 생성자로 객체를 주입받아 쓴다. 너무나 당연하게 써왔었다. 그런데 문득 궁금해졌다. "그냥 내가 만들어 쓰면 안되나?" 라는 생각이 들었다. 이 의존

    ngwdeveloper.tistory.com

     

    이전 글에서 DI가 객체 간의 결합도를 낮추는 기술임을 알게됐다. 이전 글 3.3 섹션에서 "객체의 생성과 관계 설정의 제어권이 개발자로부터 프레임워크로 넘어간 것을 제어의 역전(IoC)이라 부르며, DI는 이 IoC를 구현하는 핵심 방식이다."라고 말했었다.

    DI에 대해서는 이해했지만, 제어의 역전이라는 개념은 여전히 헷갈리게 느껴질 수 있다. 제어권이 정확히 어떻게 역전되었다는 것이며 이것이 어떤 의미가 있는지 알아보자.

     

    2. IoC가 없던 시절

    IoC를 이해하는 가장 빠른 방법은 IoC가 없던 옛 프로그래밍 방식을 찾아보는것이다.

     

    2.1. 예시 (내가 직접 운전하는 자동차)

    전통적인 방식에서 제어권은 온전히 개발자(개발자가 작성한 코드)에게 있다.

     

    - 시동: main 메서드에서 내가 직접 시동을 건다.

    - 객체 생성: 운전에 필요한 엔진, 핸들 객체를 내가 직접 new 키워드로 생성한다.

    - 실행: 내가 직접 핸들을 조작하고 엑셀을 밟아(메서드 호출) 자동차를 움직인다.

     

    2.2. 모든 제어권을 가진 main 메서드

    지난 글의 Store와 Pencil 예시를 다시 보자. 스프링 같은 프레임워크가 없다면 우리는 애플리케이션의 시작점에서 이렇게 코드를 작성해야 한다.

    public interface Product { void use(); }
    public class Pencil implements Product { /* ... */ }
    
    // 상점
    public class Store {
        private final Product product;
        public Store(Product product) { this.product = product; }
        public void doBusiness() { product.use(); }
    }
    
    // 애플리케이션의 시작점 (모든 제어권을 가짐)
    public class Application {
    
        public static void main(String[] args) {
            // 1. 개발자가 Pencil이라는 구체적인 부품을 직접 선택하고 생성한다.
            Product pencil = new Pencil();
    
            // 2. 개발자가 Store 객체를 직접 생성하며 부품을 주입한다.
            Store store = new Store(pencil);
    
            // 3. 개발자가 Store의 기능을 직접 호출한다.
            store.doBusiness();
        }
    }

    이 코드에서 모든 객체의 생성(new), 관계 설정(Store에 Pencil 주입), 실행(doBusiness)까지 모든 제어권은 Application.main 메서드, 즉 개발자에게 있다.

     

    3. IoC의 등장

    IoC는 이 패러다임을 크게 바꿔놨다.

     

    3.1. 예시 (내가 탑승한 KTX)

    IoC가 적용된 프레임워크 환경은 '내가 운전하는 자동차'가 아니라 '내가 탑승한 KTX'와 같다.

     

    - 시동: 내가 KTX의 시동을 걸지 않는다. 코레일(스프링 컨테이너)이 알아서 열차를 준비시킨다.

    - 객체 생성: 나는 그저 승객(Bean)으로서 좌석에 앉아있을 뿐이다. 열차(애플리케이션) 운행에 필요한 모든 것(기관사, 전력 등)은 코레일이 알아서 배치(주입)한다.

    - 실행: 내가 "출발"이라고 외치지 않는다. 정해진 스케줄(프레임워크의 로직)에 따라 코레일이 열차를 출발시킨다.

     

    나는 그저 승객으로서의 내 역할(비즈니스 로직)에만 충실하면 된다.

     

    3.2. IoC 컨테이너의 역할

    이 코레일의 역할을 하는 것이 바로 IoC 컨테이너(Spring Container)다.

    개발자는 Store나 Pencil 같은 객체를 @Service, @Repository 등으로 스프링 컨테이너에 등록만 한다. 그러면 IoC 컨테이너가 애플리케이션의 전반적인 제어권을 넘겨받아 다음을 수행한다.

     

    - 객체의 생성: 개발자가 new Store()를 하지 않는다. 컨테이너가 설정(@Component 등)을 읽어 직접 객체(Bean)를 생성하고 초기화한다.

    - 의존성 해결: 컨테이너가 Store 객체를 만들 때 Store가 Product를 필요로 함을 인지하고, 미리 만들어 둔 Pencil 객체를 찾아 스스로 주입(DI)한다.

    - 실행: 웹 요청이 들어오면 개발자가 만든 Store 컨트롤러를 직접 호출하는 것이 아니라, 스프링의 DispatcherServlet이 요청을 받아 해당 컨트롤러의 메서드를 호출해준다.

     

    개발자의 코드는 프레임워크에 의해 호출당하는 수동적인 존재가 된다. 이것처럼 제어권이 개발자에서 프레임워크(컨테이너)로 넘어갔기 때문에 제어의 역전이라 부른다.

     

    4. IoC와 DI의 관계

    이제 IoC와 DI의 관계가 더 명확해졌다.

     

    - IoC (제어의 역전): "제어권을 프레임워크가 갖는다"는 더 넓고 추상적인 개념이다. 객체의 생성, 생명주기 관리, 실행까지 모든 제어권이 역전되는 현상을 말한다.

    - DI (의존성 주입): IoC를 구현하기 위한 방법 중 하나다. IoC 컨테이너가 객체들의 관계를 설정 해주는 방식에 초점을 맞춘 용어이다.

     

    IoC는 DI를 포함하는 상위 개념이다. 스프링 컨테이너는 IoC 컨테이너이며 이 컨테이너가 IoC를 구현하기 위해 사용하는 핵심 메커니즘이 바로 DI 인것이다.

     

    5. 결론

    IoC를 통해 개발자는 객체 생성, 의존성 설정, 생명주기 관리라는 복잡하고 귀찮은 제어권을 포기한다.

    대신 아래와 같은 큰 이점을 얻는다.

     

    - 핵심 로직 집중: 개발자는 어떻게 만들고 연결할지가 아닌 무엇을 할지에만 집중할 수 있다.

    - 낮은 결합도: DI를 통해 객체들이 느슨하게 연결되므로(지난 글 참고), 유연하고 확장성 높은 구조를 가진다.

    - 테스트 용이성: 의존성을 쉽게 격리하고 가짜(Mock) 객체를 주입할 수 있어 단위 테스트가 용이해진다.

     

    IoC는 개발자가 비즈니스 로직에 집중하도록 도와주는 모든  Spring 기술의 출발점이라고 생각된다.

    반응형
    프로필사진

    남건욱's 공부기록