IoC·DI 복습 DI 없으면 vs 있으면 주입 3가지 @Autowired DI의 이점 📝 문제풀이
◀ 이전 📋 목차 CH 03 ▶
💉 CHAPTER 02 · 입문

IoC와 DI — 스프링의 심장을 코드로

CH 01에서 큰 그림을 잡았다면, 이번 장은 실제 코드로 파헤칩니다. "제어권이 넘어간다", "의존 객체를 주입받는다"가 구체적으로 어떤 코드인지 눈으로 확인해요. 자바를 아는 태호님껜 술술 붙습니다.

🎯 이 장을 끝내면
📖
IoC·DI 복습
한 번 더: IoC와 DI, 뭐가 다를까
CH 01의 큰 그림을 한 문장으로 다시 잡고 시작해요.

IoC(Inversion of Control, 제어의 역전)객체의 생성·연결·생명주기를 관리하는 제어권개발자에서 스프링(컨테이너)으로 넘어가는 것을 뜻해요. 원래 개발자가 하던 "언제 무엇을 만들고 연결할지"를 스프링이 대신 쥐어요. 이건 원칙(개념)이에요.

DI(Dependency Injection, 의존성 주입)는 그 IoC를 실제로 실현하는 방법이에요. 어떤 객체가 필요로 하는 다른 객체(의존성)를 스스로 new 하지 않고, 외부(스프링)가 만들어서 넣어주는(주입) 것이에요. 즉 DI는 IoC의 구체적인 구현 수단이에요.

🔤
컨테이너 — 객체(빈)를 대신 만들고 보관하며 필요한 곳에 넣어주는 '객체 보관·관리 통'이에요. 스프링에선 이 통을 IoC 컨테이너 또는 DI 컨테이너라고 불러요.
🔤
의존성(Dependency) — 어떤 객체가 일을 하려고 필요로 하는 다른 객체예요. 예를 들어 OrderService가 저장을 위해 OrderRepository를 필요로 하면, 그 저장소가 곧 의존성이에요.
🧩 IoC(원칙) vs DI(방법) IoC는 "제어권을 넘긴다"는 큰 원칙이에요.
DI는 그 원칙을 이루는 구체적 방법 중 하나예요. 한 문장 정리: "IoC를 실현하기 위해 스프링은 DI를 쓴다." 그래서 스프링의 컨테이너를 IoC 컨테이너라고도, DI 컨테이너라고도 불러요. 같은 통을 원칙 이름과 방법 이름으로 부르는 것뿐이에요.
IoC DI 의존성 생성자 주입 필드 주입 세터 주입 @Autowired 느슨한 결합 인터페이스
🧭
이번 장의 읽는 법. 개념(IoC)과 방법(DI)을 나눠서 기억하세요. 그리고 코드 비교(new vs 주입)를 눈에 익혀두면, 다음 장의 빈·컨테이너가 "아 그래서 이렇게 굴러가는구나"로 자연스레 이어집니다.
🖼️ 그림으로 보기 — IoC 컨테이너가 만들어 주입
🍃 스프링 컨테이너 (IoC) Bean UserService Bean UserRepository Controller (사용하는 곳) 주입(DI)
내가 new 하지 않아도, 컨테이너가 객체(Bean)를 만들어 두고 필요한 곳에 넣어줘요(제어의 역전 + 의존성 주입).
⚖️
DI 없으면 vs 있으면
직접 new(강한 결합) vs 주입받기(느슨한 결합)
같은 기능을 두 방식으로 짜보면 차이가 확 보여요.
🔤
결합도(Coupling) — 두 코드가 서로 얼마나 딱 붙어 의존하는지를 나타내는 말이에요. 강한 결합은 부품이 본드로 붙어 있어 하나 바꾸려면 통째로 뜯어야 하는 상태, 느슨한 결합레고 블록처럼 끼웠다 뺐다 할 수 있어 부품 교체가 쉬운 상태예요. 결합이 느슨할수록 수정과 테스트가 편해져요.
🧱 DI가 없으면 — 직접 new (강한 결합) OrderService가 필요한 OrderRepository스스로 new로 생성해요. 그러면 OrderService특정 구현 클래스에 딱 붙어버려요. 나중에 다른 저장소로 바꾸려면 이 클래스 코드를 직접 고쳐야 해요. 이게 강한 결합(tight coupling)이에요.
public class OrderService {
    // 의존 객체를 내가 직접 생성 → 강한 결합
    private OrderRepository repo = new OrderRepository();

    public void order() {
        repo.save();
    }
}
🍃 DI가 있으면 — 외부가 주입 (느슨한 결합) OrderServicenew를 하지 않아요. 필요한 객체를 생성자로 "받기만" 하고, 실제 객체는 스프링이 만들어 넣어줘요(주입). OrderService는 어떤 구현이 들어오는지 몰라도 되니 느슨한 결합(loose coupling)이 돼요.
@Service
public class OrderService {
    private final OrderRepository repo;

    // 의존 객체를 외부(스프링)가 주입해줌
    public OrderService(OrderRepository repo) {
        this.repo = repo;
    }

    public void order() {
        repo.save();
    }
}
차이의 핵심: 아래 코드에는 new OrderRepository()가 없어요. 만드는 책임을 스프링에 넘겼기 때문이에요. 이게 바로 IoC(제어의 역전)를 DI로 실현한 모습이에요.
⚠️
왜 강한 결합이 문제일까? 직접 new 하면, 구현을 바꾸거나 테스트할 때 가짜 객체로 갈아끼우기가 어려워요. 코드가 특정 클래스에 못 박혀 있으니까요. DI는 이 "갈아끼우기"를 주입 지점만 바꿔서 쉽게 만들어줘요.
💉
주입 3가지
의존성을 주입하는 세 가지 방법
생성자 · 필드 · 세터 — 그리고 왜 생성자가 권장인지.
① 생성자 주입 (Constructor Injection) — 권장 ✅ 의존 객체를 생성자의 매개변수로 받아요. 필드를 final로 둘 수 있어 한 번 주입되면 바뀌지 않고(불변), 필수 의존성이 명확해요. 생성자로 넣으니 테스트할 때 직접 객체를 넣어 만들기도 쉬워요.
@Service
public class OrderService {
    private final OrderRepository repo;   // 불변(final) 가능

    // 생성자로 필수 의존성을 받음 → 명확 + 테스트 쉬움
    public OrderService(OrderRepository repo) {
        this.repo = repo;
    }
}
② 필드 주입 (Field Injection) 필드 위에 @Autowired를 붙여 바로 주입받아요. 코드가 짧고 간편하지만, 필드를 final로 못 만들고(불변에 불리), 스프링 없이 테스트할 때 가짜 객체를 넣기가 번거로워요.
@Service
public class OrderService {
    @Autowired
    private OrderRepository repo;   // 간편하지만 테스트·불변에 불리
}
③ 세터 주입 (Setter Injection) 세터 메서드에 @Autowired를 붙여 주입받아요. 선택적(있어도 없어도 되는) 의존성이나 나중에 바꿀 수 있어야 하는 경우에 어울려요. 다만 주입 뒤에 값이 바뀔 수 있어(불변 아님) 필수 의존성엔 덜 적합해요.
@Service
public class OrderService {
    private OrderRepository repo;

    @Autowired
    public void setRepo(OrderRepository repo) {
        this.repo = repo;
    }
}
생성자 주입
불변·필수 명확·테스트 쉬움 → 권장
⚠️
필드 주입
간편하나 테스트·불변에 불리
🔧
세터 주입
선택적·교체 가능한 의존성용
💡
왜 생성자 주입이 권장일까? ① 필드를 final로 만들어 불변이 되고, ② 생성자에 매개변수가 있으니 필수 의존성이 한눈에 보이며, ③ 스프링 없이 순수 자바로 new OrderService(가짜repo) 하면 되니 단위 테스트가 쉬워요. 그래서 실무 표준은 생성자 주입이에요.
🔌
@Autowired
@Autowired: 알아서 맞는 빈을 꽂아준다
스프링이 "타입"을 보고 알맞은 객체를 찾아 주입해요.

@Autowired는 스프링에게 "여기에 맞는 빈을 찾아서 넣어줘"라고 표시하는 어노테이션이에요. 스프링은 컨테이너 안에서 타입(type)에 맞는 빈을 찾아 자동으로 주입해줘요. 개발자가 new로 만들거나 직접 연결하지 않아도 돼요.

🔤
빈(Bean) — 스프링 컨테이너가 만들어서 관리하는 객체예요. 우리가 직접 new 하지 않아도 스프링이 대신 만들어 두고, 필요한 곳에 주입해줘요. (다음 장에서 더 깊이 다뤄요.)
🔤
어노테이션(annotation) — @로 시작하는 표식이에요. 코드에 붙여 "이건 이런 역할이야"라고 스프링에게 알려줘요. (예: @Service, @Autowired)
🔎 그림으로 보기 — @Autowired가 빈을 찾아 꽂는 순서
🔌
① @Autowired 표시
"여기에 빈을 넣어줘"라고 표시해요.
🧭
② 타입 확인
필요한 타입(클래스·인터페이스)을 파악해요.
📦
③ 컨테이너 검색
그 타입에 맞는 빈을 통 안에서 찾아요.
💉
④ 자동 주입
찾은 객체를 그 자리에 넣어줘요.
✨ 생성자가 하나면 @Autowired 생략 가능 스프링 4.3 이후로는, 클래스에 생성자가 딱 하나면 그 생성자에 @Autowired붙이지 않아도 자동으로 주입돼요. 그래서 요즘 생성자 주입 코드는 어노테이션 없이 아주 깔끔해요.
@Service
public class OrderService {
    private final OrderRepository repo;

    // 생성자가 하나뿐이므로 @Autowired 생략 가능!
    public OrderService(OrderRepository repo) {
        this.repo = repo;
    }
}
참고: 롬복(Lombok)의 @RequiredArgsConstructor를 쓰면 final 필드용 생성자를 자동 생성해줘서, 생성자 코드조차 안 써도 돼요. (실무에서 자주 봐요.)
🔎
타입이 같은 빈이 둘 이상이면? 스프링은 어떤 걸 넣을지 헷갈려 해요. 그때는 @Qualifier로 이름을 지정하거나 @Primary로 우선순위를 주는 식으로 골라줘요. (지금은 "타입으로 찾는다"만 기억하면 충분해요. 뒷 장에서 다시 나와요.)
🎁
DI의 이점
DI가 주는 진짜 이득
결합도↓, 테스트 쉬움, 유연함 — 그리고 인터페이스와 만나면 더 강력해져요.
이점설명
결합도 ↓구현 클래스에 직접 new로 묶이지 않아, 구현을 교체하기 쉬워요.
단위 테스트 쉬움실제 객체 대신 가짜(Mock) 객체를 주입해서 로직만 콕 집어 테스트해요.
유연성연결(설정)을 한 곳에서 관리하니, 조합을 바꾸기가 쉬워요.
관심사 분리"객체를 만드는 일"과 "객체를 쓰는 일"이 나뉘어 코드가 깔끔해져요.
🔤
가짜(Mock) 객체 — 테스트할 때만 쓰는 대역 배우 같은 객체예요. 진짜 데이터베이스에 연결하는 대신, "저장했다고 치자"처럼 흉내만 내는 가짜를 끼워 넣으면, 느리고 번거로운 준비 없이 내 로직만 콕 집어 빠르게 검사할 수 있어요.
🔤
인터페이스(Interface) — "이런 기능이 있어야 한다"만 정해둔 규격서(설계도)예요. 구체적으로 어떻게 만드는지는 비워 두죠. 콘센트 규격에 맞으면 어떤 회사 플러그든 꽂히듯, 인터페이스에만 의존하면 어떤 구현이 와도 갈아끼울 수 있어요.
🧷 인터페이스에 의존하면 더 느슨해진다 의존을 구체 클래스가 아니라 인터페이스로 받으면, 어떤 구현이 주입돼도 상관없어져요. 실제 운영에선 진짜 구현을, 테스트에선 가짜 구현을 넣는 식으로 자유롭게 갈아끼울 수 있어요.
public interface OrderRepository {
    void save();
}

@Service
public class OrderService {
    private final OrderRepository repo;   // 인터페이스에 의존!

    public OrderService(OrderRepository repo) {  // 어떤 구현이든 주입 가능
        this.repo = repo;
    }
}
테스트에서는 new OrderService(new FakeRepository())처럼 가짜 구현을 넣어 스프링 없이도 OrderService의 로직만 검증할 수 있어요. 이게 "테스트가 쉬워진다"의 실체예요.
🌟
정리하면, DI는 단순히 코드를 줄이는 게 아니라 결합을 느슨하게 만들어 "바꾸기 쉽고 테스트하기 쉬운" 구조를 선물해요. 이게 스프링이 사랑받는 핵심 이유예요.
🧠 이 장 핵심 요약
📝
문제풀이 · 점검
배운 걸 가볍게 점검해봐요
시험이 아니라 "내가 이해했나" 확인용이에요. 틀려도 바로 해설이 나와요.
🧪
문제를 풀면 즉시 정답과 해설이 나오고, 위쪽 바에 점수가 쌓여요. 네 가지 유형(객관식 · O/X · 빈칸 · 개념/코드)을 섞어 두었어요.
CH 03 🫘 빈(Bean)과 컨테이너 — 스프링이 관리하는 객체의 정체