한창 Spring을 공부하고 있는 와중에
"스프링은 IoC 컨테이너로 빈을 관리하며 DI를 사용한다"
이런 문장을 본 적 있을 것이다.

그렇다면 대체 IoC와 DI는 무엇일까?
지금 바로 알아보도록 하자

객체의 생성과 의존성 관리 권한을 개발자가 아닌 외부 프레임워크나 컨테이너에 맡기는 설계 원칙
제어의 역전
-> 기존에는 객체가 스스로 필요한 의존성을 만들고 관리했지만 IoC에서는 외부가 대신 관리함.
결합도 감소
-> 객체가 직접 다른 객체를 생성하지 않고 외부에서 주입받으므로, 코드 간 의존성이 줄어듦.
유연성 증가
-> 객체 교체나 확장이 쉬워지고 테스트 시 Mock 객체를 활용하기도 편함.
(여기서 Mock은 실제 객체 대신 동작을 흉내내도록 만든 가짜 객체)
이해가 쉽도록 현실에 대입해 생각해보자.
현실에서 내가 요리를 해서 밥을 먹으려면 재료를 사고, 손질하고, 조리까지 모든 제어권이 나에게 있지. 이건 전통적 방식으로 제어권이 모두 개발자에게 있는 거야.
근데 레스토랑에 가게 되면 셰프가 알아서 재료를 준비하고 요리를 만들어줘.
그럼 나는 단지 주문만 하게 되면 제어권이 셰프, 즉 외부 컨테이너에서 대신 준비해주기 때문에 용이성을 높일 수 있다.

외부에서 두 객체 간의 관계를 결정해주는 디자인 패턴
Spring 프레임 워크는 3가지 핵심 프로그래밍 모델을 지원하고 있는데 그 중 하나가 의존성 주입이다.
인터페이스를 사이에 둬서 클래스 레벨에서는 의존 관계가 고정되지 않도록 하고 런타임 시에 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮출 수 있게 해준다.
한 객체가 다른 객체를 사용할 때 의존성이 있다고 한다.
public class Store{
private Pencil pencil;
}
이 코드를 보면 Store 객체가 Pencil 객체를 사용하고 있는 경우에 Store 객체가 Pencil 객체에 의존성이 있다고 표현한다,

두 객체 간의 관계(의존성)을 맺어주는 것을 의존성 주입이라 하며 생성자 주입, 필드 주입, 수정자 주입 등 다양한 주입 방법이 있다.

그렇다면 의존성 주입이 필요한 이유가 무엇일까?
public class Store {
private Pencil pencil;
public Store() {
this.pencil = new Pencil();
}
}
이 코드에서 클래스는 두 가지 문제를 가지고 있다.
1. 두 클래스가 강하게 결합됨.
2. 객체들 간의 관계가 아니라 클래스 간의 관계가 맺어짐.
⚠️ 현재 코드의 한계점
현재
Store클래스는Pencil클래스와 강하게 결합되어 있습니다.
Store 클래스의 생성자를 직접 수정해야 합니다.Store 클래스가 파생되어 유지보수가 어려워집니다.💡 해결책: 객체를 내부에서 직접 생성하지 않고, 인터페이스를 통해 외부에서 주입(DI)받는 방식으로 설계를 개선해야 합니다.
🧐 클래스 간의 관계 vs 객체 간의 관계
현재 설계의 근본적인 문제는 관계가 클래스 단위로 고정되어 있다는 점입니다.
올바른 객체지향 설계로 나아가기 위해서는 다음과 같은 변화가 필요합니다.
Store 클래스가 Pencil 클래스를 직접 참조하면, 컴파일 시점에 관계가 결정되어 버립니다 (정적 관계).Product)에 의존하게 하여, 실행 시점(Runtime)에 실제 객체와 연결되도록 해야 합니다.Store는 상대방이 Pencil인지 Food인지 알 필요가 없습니다. Product라는 인터페이스 타입으로만 소통하면 됩니다. 이를 통해 새로운 상품이 추가되어도 Store의 코드는 변경되지 않습니다.Store가 "어떤 제품을 팔지(객체 생성)"와 "어떻게 팔지(비즈니스 로직)"를 모두 책임짐.Store는 "판매 로직"에만 집중하고, "어떤 제품을 팔지"는 외부(DI 컨테이너 등)에 맡겨 관심을 완벽히 분리해야 합니다.이 내용을 바탕으로 DI로 해결해보겠다.
위와 같은 문제를 해결하기 위해선 '다형성'이 필요하다. Pencil, Food 등 여러 가지 제품을 하나로 표현하기 위해 Product라는 Interface가 필요하다. 그럼 우선 Pencil에서 Product 인터페이스를 구현해보도록 하자.
public interface Product{
}
public class Pencil implements Product{
}
이제 우리는 Store와 Pencil이 강하게 결합되어 있는 부분을 제거해주어야 한다.
이를 위해선 다음과 같이 외부에서 상품을 주입받아야 한다.(Injection)
그래야 Store에서 구체 클래스에 의존하지 않게 된다.
public class BeadFactory{
public void store(){
// 빈의 생성
product pencil = new Pencil();
// 의존성 주입
Store store = new Store(pencil);
}
}
이러한 부분은 스프링 프레임 워크가 완벽하게 지원한다.
결국 스프링을 사용한다는 것은, 복잡한 객체 간의 관계 설정을 프레임워크에 맡기고 개발자는 핵심 비즈니스 로직에만 집중할 수 있는 환경을 만드는 것이다.
| 구분 | IoC (Inversion of Control) | DI (Dependency Injection) |
|---|---|---|
| 개념 | 제어의 역전 (상위 설계 원칙) | 의존성 주입 (구현 메커니즘) |
| 핵심 질문 | "프로그램의 흐름을 누가 제어하는가?" | "객체가 필요한 자원을 어떻게 얻는가?" |
| 관점 | 전체적인 권한의 이동 (개발자 → 프레임워크) | 구성 요소 간의 연결 방식 (외부에서 주입) |
| 목적 | 프로그램 제어권의 분리 및 모듈화 | 결합도 낮춤, 코드 재사용 및 테스트 용이성 |
| 실제 사례 | 프레임워크가 @Test 메서드를 알아서 실행 | 생성자를 통해 Service에 Repository 전달 |
마지막으로 한 줄 요약을 해보자면 "IoC는 '제어권을 넘긴다'는 큰 그림이고, DI는 그 그림을 그리기 위해 '객체를 외부에서 넣어주는 도구'입니다." 라고 정리할 수 있다.

오오오옹 gpt 잘 봤습니다.