DI 클래스·생성자📚 전체 맵DI 3방식
07c · DI 완전 이해 · 시리즈 3/5

IOC &
CONTAINER

IoC 개념 · Spring Container가 Bean을 만드는 순서 · 의존성 그래프 · Singleton 보장 원리 · ApplicationContext 구조

Big Picture First
지금 어디 있나 — 전체 흐름 속 위치
① 클래스·필드·생성자·final (07b)
② IoC + Spring Container ← 지금 여기
③ DI 3가지 방식 (07d)
④ 실제 코드에서 DI (07e)
📌 07c에서 배울 것

07b에서 클래스·생성자를 이해했습니다. 이제 "스프링이 어떻게 그 생성자를 자동으로 호출하는가"를 봅니다.
IoC(제어의 역전) → Spring Container → Bean 스캔 → 의존성 분석 → Bean 생성 순서 결정 → DI 실행 → Singleton 유지.
이 파일을 이해하면 "스프링이 알아서 해준다"가 구체적으로 보입니다.

IoC
IoC(Inversion of Control) — 제어의 역전

DI의 상위 개념. DI는 IoC를 구현하는 방법 중 하나입니다. IoC를 모르면 "왜 스프링이 개발자 대신 객체를 만드나"가 이해가 안 됩니다.

❌ 전통적 제어 흐름 (개발자가 제어)
개발자가 직접 제어
// 개발자가 모든 객체 생성을 직접 제어
public static void main(String[] args) {
    // 개발자가 순서 직접 결정
    PaymentService ps =
        new PaymentService(); // 직접 생성

    OrderService os =
        new OrderService(ps); // 직접 주입

    os.order();
}

개발자가 어떤 객체를 언제 만들지, 어떤 순서로 주입할지 전부 직접 결정.
코드가 복잡해질수록 의존 관계 관리가 매우 힘들어짐.
테스트할 때마다 전체 설정을 바꿔야 함.

✅ IoC (스프링이 제어)
스프링이 제어 (개발자는 정의만)
// 개발자는 정의만 함
@Service
public class PaymentService { ... }

@Service
public class OrderService {
    public OrderService(PaymentService ps) {
        this.ps = ps;
    }
}
// 생성, 주입, 순서 → 스프링이 알아서 결정

제어권이 개발자에서 스프링으로 역전(Inversion)됨.
개발자는 "이런 클래스 있고, 이렇게 연결해줘" 표시만.
실제 생성·주입·순서는 스프링이 담당.

📌 IoC vs DI — 관계 정리

IoC (제어의 역전): 개념. "제어권을 개발자에서 프레임워크로 넘긴다"는 원칙.
DI (의존성 주입): 구현. IoC를 실현하는 방법 중 하나. 의존 객체를 외부에서 주입받음.

DI는 IoC의 구체적인 구현 방식입니다. IoC 없이는 DI도 없습니다.
스프링 컨테이너가 바로 IoC 컨테이너이고, DI를 자동으로 처리해주는 주체입니다.

Bean Lifecycle
Spring Container — Bean을 만드는 전체 순서

SpringApplication.run()이 실행되면 스프링이 내부에서 어떤 순서로 일하는지 전부 봅니다. 각 단계에서 JVM 메모리가 어떻게 변하는지 함께 추적합니다.

1
SpringApplication.run()
ApplicationContext 생성 — Spring Container 시작
main()에서 SpringApplication.run(BoardApplication.class, args) 실행 →
Spring Container(ApplicationContext) 객체 Heap에 생성 →
이 컨테이너가 이후 모든 Bean을 관리하는 창고 역할.
HEAP — ApplicationContext 객체 생성
2
ComponentScan
@Component 계열 어노테이션 스캔 — 관리 대상 목록 작성
@SpringBootApplication 위치의 패키지부터 하위 패키지 전체 탐색 →
@Component, @Service, @Repository, @Controller, @RestController, @Configuration 찾기 →
찾은 클래스들을 "Bean으로 만들 후보 목록"에 추가.
이 단계는 아직 객체를 만드는 게 아님. 목록 작성만.
METHOD AREA — 클래스 정보 읽기
3
BeanDefinition 등록
각 클래스의 Bean 정의 생성 — "어떻게 만들지" 설계
스캔한 각 클래스에 대해 BeanDefinition 생성:
- 클래스 이름은 무엇인가
- 생성자가 무엇인가, 어떤 의존성이 필요한가
- Scope는 무엇인가 (기본: Singleton)
- 초기화/소멸 메서드가 있는가
아직 실제 객체는 없음. 레시피(설계)만.
HEAP — BeanDefinition 객체들 (레시피)
4
의존성 그래프 분석
누가 누구에게 의존하는지 파악 — Bean 생성 순서 결정
각 Bean의 생성자를 분석 →
"OrderService는 PaymentService가 필요하다" 파악 →
의존성 그래프(Dependency Graph) 작성
의존받는 것(잎 노드)부터 먼저 만들 순서 결정.

예: PaymentService ← OrderService 방향이면
PaymentService 먼저, OrderService 나중.
HEAP — 의존성 순서 분석 (내부 자료구조)
5
Bean 생성 (순서대로)
의존성 없는 Bean부터 → 생성자 호출 → Heap에 객체 생성
Bean 생성 내부 동작
// [5-1] PaymentService: 의존성 없음 → 먼저 생성
//       내부: new PaymentService()
//       → Stack: 기본 생성자 프레임 push/pop
//       → Heap: PaymentService 객체 생성 (주소: 0x1A2B)
//       → ApplicationContext에 등록: "paymentService" → 0x1A2B

// [5-2] OrderService: PaymentService 필요 → 5-1 완료 후 생성
//       내부: new OrderService(paymentServiceBean)
//             ↑ 5-1에서 만든 0x1A2B를 생성자에 넣음
//       → Stack: 생성자 프레임 push
//       →         this.paymentService = 0x1A2B (DI 실행!)
//       → Stack: 생성자 프레임 pop
//       → Heap: OrderService 객체 생성 (주소: 0x3C4D)
//              OrderService.paymentService = 0x1A2B 연결됨
//       → ApplicationContext에 등록: "orderService" → 0x3C4D
HEAP — Bean 객체들 생성 완료 STACK — 생성자 프레임들 push/pop
6
후처리 (BeanPostProcessor)
AOP Proxy 생성, @PostConstruct 실행 등
Bean 생성 후 추가 처리:
- @Transactional이나 @Aspect 대상 Bean → CGLIB Proxy 객체로 교체
- @PostConstruct 붙은 초기화 메서드 실행
이때 원본 Bean 대신 Proxy Bean이 ApplicationContext에 등록됨.
HEAP — Proxy 객체들 생성 (원본 Bean 대체)
7
완료
모든 Bean 준비 완료 — 요청 처리 시작 가능
ApplicationContext에 모든 Bean이 등록됨 →
Tomcat 서버 시작 → 포트 LISTEN →
이후 요청이 올 때마다 ApplicationContext에서 Bean을 꺼내서 사용.
새로 만드는 게 아니라 이미 Heap에 있는 객체 재사용 (Singleton).
HEAP — 모든 Bean 상주, 요청마다 재사용
Dependency Graph
의존성 그래프 — Bean 생성 순서가 결정되는 원리
의존성 없는 것(잎 노드)부터 만들고, 의존하는 것(뿌리)을 나중에 만든다
의존성 방향 (화살표: A → B = A가 B에 의존)
UserController
→ 의존 →
UserService
UserService
→ 의존 →
UserRepository
+ BCryptPasswordEncoder
UserRepository
→ 의존 없음 (잎 노드)
Bean 생성 순서 (역방향 — 잎 노드부터)
1
UserRepository
의존 없음 → 먼저 생성
2
BCryptPasswordEncoder
의존 없음 → 먼저 생성
3
UserService
1,2 완료 후 생성 가능
4
UserController
3 완료 후 마지막 생성
📌 스프링이 의존성 순서를 파악하는 방법

스프링은 생성자의 매개변수를 분석합니다.
UserService(UserRepository repo, BCryptPasswordEncoder encoder)를 보면 →
"UserService를 만들려면 UserRepository와 BCryptPasswordEncoder가 먼저 있어야 한다"고 파악.

재귀적으로 의존성을 분석해서 최종 생성 순서를 결정합니다.
그래서 개발자가 생성 순서를 신경 쓰지 않아도 됩니다.

Singleton
Singleton — 스프링이 Bean을 하나만 만드는 이유와 원리
Singleton 동작 원리 — ApplicationContext 내부
// ApplicationContext 내부 (개념적 코드)
public class ApplicationContext {
    // Bean들을 이름 → 객체 로 저장하는 Map
    private final Map<String, Object> singletonBeans = new HashMap<>();

    public Object getBean(String beanName) {
        // 이미 만들어진 Bean이 있으면 그걸 반환
        if (singletonBeans.containsKey(beanName)) {
            return singletonBeans.get(beanName); // 기존 객체 반환
        }
        // 없으면 새로 만들어서 저장 후 반환
        Object bean = createBean(beanName);
        singletonBeans.put(beanName, bean); // 저장 → 이후 재사용
        return bean;
    }
}

// 결과:
// userService 요청 1번 → new UserService() → Heap 0x3C4D → 저장
// userService 요청 2번 → 이미 있음 → 0x3C4D 반환 (new 안 함)
// userService 요청 1000번 → 여전히 0x3C4D 반환
// → 항상 같은 객체 → Singleton 보장
📌 Singleton이기 때문에 주의할 점 — 상태(State) 금지

Bean이 Singleton(딱 하나)이므로 → 동시에 수천 개의 요청이 같은 Bean 객체를 사용 →
Bean 안에 상태를 저장하면 안 됩니다 (멀티스레드 문제).

나쁜 예: private int requestCount = 0; → 여러 스레드가 동시에 읽고 쓰면 값이 뒤섞임.
좋은 예: 메서드의 지역변수 사용 → Stack에 저장 → 스레드마다 독립적 → 안전.

DI로 주입받은 final 필드는 초기화 후 읽기만 하므로 안전합니다.

Scope 종류설명사용 시점메모리
singleton (기본) 컨테이너에 딱 하나 Service, Repository, Controller — 대부분 Heap — 서버 살아있는 한 유지
prototype 요청할 때마다 새 객체 상태를 가진 객체 (거의 안 씀) Heap — 요청마다 생성, GC 대상
request HTTP 요청마다 새 객체 웹 요청별 상태 (거의 안 씀) Heap — 요청마다 생성, 요청 끝나면 GC
@Bean Method
@Configuration + @Bean — 직접 Bean 등록하는 방법

@Service/@Component는 클래스에 붙이면 스프링이 자동 스캔합니다. @Configuration + @Bean은 개발자가 직접 Bean 생성 방법을 코드로 정의할 때 씁니다. 3차 프로젝트의 SecurityConfig, BCryptPasswordEncoder 등록이 이 방식입니다.

@Configuration + @Bean — 동작 원리
@Configuration
// ↑ 이 클래스가 Bean 설정 클래스임을 표시
//   @Component 계열이므로 스프링이 스캔해서 Bean으로 등록
//   내부적으로 CGLIB Proxy로 만들어짐 (Singleton 보장 위해)
public class AppConfig {

    @Bean
    // ↑ 이 메서드의 반환값을 Bean으로 등록
    //   메서드 이름이 Bean 이름이 됨 (passwordEncoder)
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
        // 스프링이 이 메서드를 딱 한 번 호출 → 반환된 객체를 Heap에 저장
        // 이후 BCryptPasswordEncoder 필요한 곳에 자동 주입
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // HttpSecurity는 스프링이 자동 주입 (메서드 매개변수 DI)
        return http
            .authorizeHttpRequests(...)
            .build();
        // 반환된 SecurityFilterChain → Bean으로 등록
    }
}

// 실행 시점:
// 스프링 기동 시 → AppConfig Bean 생성 → @Bean 메서드들 실행
// → passwordEncoder() 실행 → BCryptPasswordEncoder 객체 → Heap
// → filterChain() 실행  → SecurityFilterChain 객체 → Heap
Back to Big Picture
전체 흐름 재확인 — 07c에서 배운 것 연결
① 클래스 읽기 → Method Area
② ComponentScan → 관리 대상 목록
③ 의존성 그래프 분석 → 순서 결정
④ 잎 노드 Bean 먼저 생성 → Heap
⑤ 생성자에 주입(DI) → Heap 연결
⑥ Singleton 보장 → 재사용
📌 07c 핵심 요약
  • IoC: 제어권이 개발자에서 스프링으로 역전. 개발자는 정의만, 스프링이 생성·주입 담당.
  • Spring Container: ApplicationContext. Heap에 존재. 모든 Bean을 Map으로 관리.
  • ComponentScan: @Service/@Component 찾아서 Bean 후보 목록 작성.
  • Bean 생성 순서: 생성자 분석 → 의존성 그래프 → 잎 노드 먼저 → 재귀적으로 생성.
  • Singleton: Bean은 Heap에 딱 하나. ApplicationContext의 Map에서 관리. 재사용.
  • @Configuration + @Bean: 직접 Bean 생성 로직을 코드로 정의할 때 사용. SecurityConfig 등.
DI 클래스·생성자📚 전체 맵DI 3방식