심화/Spring

[Spring] IoC(제어의 역전), DI(의존성 주입)

annovation 2025. 10. 1. 10:00

IoC와 DI

 

 처음 IoC와 DI에 대한 개념을 들었던게 작년 말에 부트캠프 들었을 때였는데, 그때만해도 Spring이 뭔지도 모르고 Java도 처음써보고 CS 관련 지식도 없었어서 대체 이게 무슨 뜻인지 어떻게 공부해야하는지도 막막했었다. Java랑 Spring에 관련된 중요한 개념인 것 같았는데 어떻게 공부해야할지를 몰라서 뭔지 엄청 궁금했었는데 이제 어떤건지 알게돼서 1년 묵은 체증이 날아갈 것 같다.

 

💡IOC와 DI

  • Spring 으로 개발을 입문한 분들이 처음에 가장 많이 오해하는 것이 바로 IoC와 DI가 Spring에서 처음으로 만든 기능이라고 생각하는 것
  • IoC, DI는 객체지향의 SOLID 원칙 그리고 GoF의 디자인 패턴과 같은 설계 원칙 및 디자인 패턴이다!
  • IoC는 설계 원칙에 해당하고 DI는 디자인 패턴에 해당한다.

💡설계 원칙 예시

 

👩‍🍳 예시 : 맛있는 김치 볶음밥을 만들기 위한 원칙

  • 신선한 재료를 사용한다.
  • 신 김치를 사용한다.
  • 밥과 김치의 비율을 잘 맞춰야 한다.
  • 볶을 때 재료의 순서가 중요하다.

💡디자인 패턴 예시

 

🍳 예시 : 맛있는 김치 볶음밥을 만들기 위한 황금 레시피

  1. 오일을 두른 팬에 채썬 파를 볶아 파기름을 만든다.
  2. 준비한 햄을 넣고 볶다가, 간장 한스푼을 넣어 풍미를 낸다.
  3. 설탕에 버무린 김치를 넣고 함께 볶는다.
  4. 미리 식혀 둔 밥을 넣어 함께 볶는다.
  5. 참기름 한스푼을 넣어 마무리한다.

Spring에서의 IoC와 DI

 

💡좋은 코드를 위한 Spring의 IoC와 DI

 

💻 좋은 코드란 무엇일까?

  • 논리가 간단해야 한다.
  • 중복을 제거하고 표현을 명확하게 한다.
  • 코드를 처음 보는 사람도 쉽게 이해하고 수정할 수 있어야 한다.
  • 의존성을 최소화해야 한다.
  • 새로운 기능을 추가 하더라도 크게 구조의 변경이 없어야 한다.
  • etc…

➡️ 이렇듯 좋은 코드를 작성하기 위해서는 신경써야 할 부분이 정말 많다.

  • 따라서 Spring은 개발자가 Java를 사용하여 쉽게 좋은 코드를 작성할 수 있도록 도와주는 역할을 해준다.
  • 실생활에 비교해 보자면 요리도구까지 전부 들어있는 밀키트라고 할 수 있다.
  • 여기서 IoC와 DI는 좋은 코드 작성을 위한 Spring의 핵심 기술 중 하나이다.

💡Spring의 IoC와 DI란?

  • Spring Docs에 나와있는 IoC와 DI [각주:1]
  • IoC에 대해 ‘IoC는 DI로도 알려져 있다’ 라고 소개하고 있다.
  • 'DI 패턴을 사용하여 IoC 설계 원칙을 구현하고 있다’ 라고 이해할 수 있다.

DI(의존성 주입)

 

먼저 DI를 이해하려면 ‘의존성’에 대한 이해가 필요하다.

 

💡의존성이란?

  • 의존성은 어떤 객체가 동작하기 위해 다른 객체를 필요로 하는 관계
  • 예를 들어, 사람이 다리를 다쳤을 때 목발 없이는 걸을 수 없다면, 그 사람은 목발에 의존하고 있는 것

🔎 Java Class 예시

public class Consumer {

    void eat() {
        Chicken chicken = new Chicken();
        chicken.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat();
    }
}

class Chicken {
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}
  • 만약 Consumer가 치킨 대신 피자를 먹고 싶다면?
  • Consumer 내부의 코드를 직접 수정해야 하고, 코드 변경 범위가 커짐
  • 여기서 Consumer Chicken에 직접 의존하고 있다고 말할 수 있다.
  • 이런 관계를 강한 결합 구조라고 부르며, 이는 유지보수성과 확장성이 떨어진다.

🔎 의존성 문제 해결 방법

  • Java 의 Interface 를 활용하면 이를 해결할 수 있다!
public class Consumer {

    void eat(Food food) {
        food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.eat(new Chicken());
        consumer.eat(new Pizza());
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}
  • 이처럼 Interface 다형성의 원리를 사용하여 구현하면 고객이 어떠한 음식을 요구하더라도 쉽게 대처할 수 있다.
    • 다형성의 원리 : 코드는 하나지만 실행되는 결과는 구현체에 따라 달라지는 것
  • Consumer는 구체 클래스(Chicken, Pizza)가 아니라 Food라는 인터페이스에만 의존하기 때문에 새로운 음식 (Pizza, Burger, Sushi)이 추가되더라도 Consumer를 수정할 필요가 없다.
  • 이런 관계를 약한 결합(Loose Coupling), 즉 약한 의존성이라고 부른다.

💡주입이란?

  • 우리가 주사기를 통해 백신을 우리 몸속에 주입 하듯이, 코드에서의 주입도 마찬가지로 여러 방법을 통해 필요로 하는 객체를 해당 객체에 전달하는 것이다.
  • 다음 3가지 주입 형태에 대해 알아보자 (필드에 직접 주입, 메서드를 통한 주입, 생성자를 통한 주입)

1️⃣ 필드에 직접 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.food = new Chicken();
        consumer.eat();

        consumer.food = new Pizza();
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}
  • 이처럼 Food를 Consumer에 포함 시키고 Food에 필요한 객체를 주입받아 사용할 수 있다.

2️⃣ 메서드를 통한 주입

public class Consumer {

    Food food;

    void eat() {
        this.food.eat();
    }

    public void setFood(Food food) {
        this.food = food;
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer();
        consumer.setFood(new Chicken());
        consumer.eat();

        consumer.setFood(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}
  • 이처럼 set 메서드를 사용하여 필요한 객체를 주입받아 사용할 수 있다.

3️⃣ 생성자를 통한 주입

public class Consumer {

    Food food;

    public Consumer(Food food) {
        this.food = food;
    }

    void eat() {
        this.food.eat();
    }

    public static void main(String[] args) {
        Consumer consumer = new Consumer(new Chicken());
        consumer.eat();

        consumer = new Consumer(new Pizza());
        consumer.eat();
    }
}

interface Food {
    void eat();
}

class Chicken implements Food{
    @Override
    public void eat() {
        System.out.println("치킨을 먹는다.");
    }
}

class Pizza implements Food{
    @Override
    public void eat() {
        System.out.println("피자를 먹는다.");
    }
}
  • 이처럼 생성자를 사용하여 필요한 객체를 주입받아 사용할 수 있다.

IoC(제어의 역전)

 

💡제어란?

  • 보통은 객체가 스스로 필요한 걸 직접 만든다는 흐름
class Consumer {
    void eat() {
        Food food = new Chicken();  // Consumer가 직접 만든다
        food.eat();
    }
}
  • 여기서 제어 흐름 Consumer에서 Food라고 말할 수 있다.
  • 즉, Consumer가 Food를 만들고, Food를 실행하는 모든 제어권을 쥐고 있음
  • 이러한 강한 결합의 단점은 앞서 살펴 봤듯이, Consumer가 특정 Food(Chicken)에 강하게 묶임 → Pizza를 먹으려면 Consumer를 고쳐야 함

💡제어의 역전(Inversion of Control, IoC)이란?

  • IoC는 말 그대로 객체가 스스로 제어하지 않고, 외부에서 필요한 걸 주입받는다는 개념
class Consumer {
    void eat(Food food) {  // Food는 외부에서 넣어준다
        food.eat();
    }
}

Consumer consumer = new Consumer();
consumer.eat(new Chicken());  // Chicken을 외부에서 주입
consumer.eat(new Pizza());    // Pizza를 외부에서 주입
  • 이제 Consumer는 Food를 직접 만들지 않고, Food를 밖에서 만들어서 Consumer에게 전달해준다.
  • 제어권이 Consumer에서 벗어나 외부(Spring 같은 컨테이너)로 넘어간 것
  • 따라서 제어의 흐름이 Food → Consumer로 뒤집혔다고 표현

출처

https://teamsparta.notion.site/2-3-IoC-DI-2252dc3ef514810e84d7c5f85925fbe7

 

챕터2-3 : IoC(제어의 역전), DI(의존성 주입) 이해하기 | Notion

Spring의 IoC와 DI

teamsparta.notion.site


주석 출처

'심화 > Spring' 카테고리의 다른 글

[Spring] JPA란 무엇일까?  (0) 2025.10.03
[Spring] IoC Container와 Bean  (0) 2025.10.02
[Spring] JDBC란 무엇일까?  (0) 2025.09.30
[Spring] MySQL과 IntelliJ 연동하기 (Mac)  (0) 2025.09.29
[Spring] DTO란?  (0) 2025.09.26