Spring IoC & DI 완전 정복: Chapter 6. Spring은 의존성을 어떻게 해결할까?

2026. 7. 22. 15:04·🍃SpringBoot

들어가며: "Bean은 만들어졌다. 그런데 Spring은 수많은 Bean 중에서 어떤 객체를 찾아 연결할까?"

Bean을 생성하는 5단계 파이프라인

앞 장에서는 Spring이 BeanDefinition을 기반으로 Bean을 생성하는 5단계 파이프라인을 살펴보았다. 그러나 객체를 인스턴스화(Instantiate)했다고 해서 곧바로 사용할 수 있는 것은 아니다. 객체지향 세계에서 단독으로 모든 일을 처리하는 객체는 드물다. OrderService는 PaymentService가 필요하고, PaymentService는 PaymentGateway가 필요하다.

따라서 Bean은 메모리에 올리는 것만으로는 부족하며, 필요한 의존성을 찾아 서로 연결(Link)해야 비로소 하나의 객체 그래프(Object Graph)가 완성된다. Spring은 이 과정을 Dependency Injection(DI)이라 부른다. DI는 개발자에게 단순한 '편의 기능'이 아니다. Spring Container 입장에서는 흩어져 있던 BeanDefinition과 객체들을 하나의 살아있는 시스템으로 조립하는 핵심 메커니즘이다. 이번 장에서는 Spring이 의존성을 어떤 기준으로 탐색하고 내부적으로 어떤 순서로 해결하는지 파헤쳐 본다.

6.1 Dependency Injection은 언제 수행될까?

Bean 생성 파이프라인에서 DI가 일어나는 정확한 위치는 2단계인 Populate 단계다.

1단계인 Instantiate 단계에서는 생성자만 호출되어 객체의 껍데기만 생성된다.
그러나 다음과 같은 생성자 주입 코드를 떠올려 보자.

public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

Spring이 OrderService 인스턴스를 만들려면, 생성자 호출 시점에 이미 PaymentService가 준비되어 있어야 한다. 즉, 객체를 생성하는 순간과 의존성을 해결하는 순간이 서로 밀접하게 얽혀 있다. 따라서 Populate 단계는 단순한 "필드에 값을 세팅하는 과정"이 아니라, 객체 생성과 맞물려 클래스 간의 연결 고리를 완성하는 단계로 이해해야 한다.

6.2 Spring은 무엇을 기준으로 의존성을 찾을까?

Spring이 다음과 같은 생성자 주입 지점을 마주했다고 가정해 보자.

public OrderService(PaymentService paymentService)

Spring은 단순히 "PaymentService라는 Bean을 가져와라"고 요청하지 않는다.
파라미터와 주입 지점을 정밀하게 분석하여 다음과 같은 조건표를 작성한다.

  1. 요구 타입: `PaymentService.class`
  2. 필수 여부: `required = true`
  3. 한정자 식별자: `@Qualifier` 부착 여부 및 값
  4. 주입 형태: `@Lazy`, `Optional`, `Provider`, `Collection` 주입 여부

Spring은 주입에 필요한 조건들을 묶어 DependencyDescriptor라는 별도의 메타데이터 객체로 변환한다.
이 객체가 바로 이후 진행될 의존성 탐색의 명확한 기준표가 된다.

6.3 의존성의 설계도: DependencyDescriptor

BeanDefinition이 "Bean을 어떻게 만들 것인가"에 대한 설계도라면,
DependencyDescriptor는 "주입 지점이 어떤 Bean을 요구하는가"에 대한 설계도다.

public OrderService(
    @Qualifier("tossPayment")
    PaymentService paymentService
)

위 코드에서 Spring이 생성하는 DependencyDescriptor 내부 메타데이터 구조를 시각화하면 다음과 같다.

Spring은 주입 지점마다 DependencyDescriptor를 만든 뒤,
이를 핵심 탐색 엔진으로 넘겨 조건을 만족하는 Bean을 찾도록 명령한다.

6.4 의존성 탐색 프로세스

DependencyDescriptor가 준비되면 Spring Container는 본격적인 탐색 과정에 들어간다.
기본적으로 Spring의 의존성 해결은 타입 기반(Type Matching)으로 작동한다.

가장 먼저 DependencyDescriptor에 정의된 타입과 일치하거나 해당 타입을 상속/구현한 모든 Bean을 Container 목록에서 뒤져 1차 후보군을 만든다.

6.5 타입만으로 충분하지 않을 때 (다중 후보 경합)

만약 PaymentService 인터페이스를 구현한 Bean이 2개 이상 존재하면 어떻게 될까?

두 구현체 모두 PaymentService 타입이므로 1차 타입 매칭 결과 후보가 2개 추출된다. 어떤 객체를 넣어야 할지 결정할 수 없게 된 Spring은 NoUniqueBeanDefinitionException 예외를 던지며 구동을 멈춘다. 이 경합을 해결하기 위해 Spring은 다음과 같은 3단계 우선순위 규칙을 적용한다.

  1. @Qualifier: 주입 지점의 Qualifier 값과 일치하는 혜택을 가진 Bean을 선택한다.
  2. @Primary: 클래스 상단에 @Primary가 선언된 "기본 Bean"을 선택한다.
  3. Bean 이름 매칭: 파라미터나 필드의 변수명(예: tossPayment)과 일치하는 Bean 이름을 가진 객체를 선택한다.

6.6 의존성 해결의 심장: DefaultListableBeanFactory

Spring Container 내부에서 이 모든 탐색과 결정을 총괄하는 주체가 바로 DefaultListableBeanFactory다.

이 클래스는 BeanDefinition 저장소이자 실질적인 Bean 공장이다.
의존성 해결 요청이 들어오면 내부적으로 `doResolveDependency()` 메서드를 실행한다.

// DefaultListableBeanFactory.java (개념적 재구성)
public Object doResolveDependency(DependencyDescriptor descriptor, String requestingBeanName, ...) {
    
    // 1. Descriptor에서 요구 타입 추출
    Class<?> type = descriptor.getDependencyType();
    
    // 2. 타입에 해당하는 모든 후보 Bean 검색
    Map<String, Object> matchingBeans = findAutowireCandidates(type);
    
    // 3. 단일 후보일 경우 즉시 반환
    if (matchingBeans.size() == 1) {
        return matchingBeans.values().iterator().next();
    }
    
    // 4. N개 후보일 경우 @Qualifier, @Primary, 이름 매칭으로 경합 해결
    String autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
    return matchingBeans.get(autowiredBeanName);
}

`doResolveDependency()`는 DependencyDescriptor를 전달받아 타입 검색, 다중 후보 경합, Lazy 주입 처리 등을 모두 수행한 뒤 완벽히 일치하는 단 하나의 Bean 인스턴스를 반환한다.

6.7 DI는 '객체 그래프'를 완성하는 과정이다

DI를 단순히 "필요한 객체를 파라미터로 넣어주는 기술"로 바라보는 것은 프레임워크의 역할을 너무 좁게 해석한 것이다.

Spring Container 입장에서 DI는 메모리상에 파편화되어 존재하던 객체들을 하나의 거대한 '객체 그래프(Object Graph)'로 완성하는 행위다.

Bean A, Bean B, Bean C가 각각 생성된 시점에는 시스템이 작동할 수 없다. Container가 doResolveDependency()를 이용해 의존 관계를 찾고 이들을 단단하게 연결해 주어야만 비로소 애플리케이션으로서의 생명력을 얻게 된다.

Chapter 6 핵심 정리

  1. Populate 단계와 객체 연결
    DI는 Bean 생성 파이프라인의 Populate 단계에서 수행되며, 흩어진 객체들을 연결해 객체 그래프를 완성한다.
  2. 의존성 설계도 (DependencyDescriptor)
    Spring은 주입 지점의 요구 조건(타입, Qualifier, 필수 여부 등)을 분석하여 DependencyDescriptor라는 메타데이터 객체를 생성한다.
  3. 타입 우선 탐색 (Type Matching)
    의존성 탐색의 기본 기준은 Bean의 이름이 아닌 타입이다.
  4. 다중 후보 경합 해결
    동일한 타입의 Bean이 여러 개 검색되면 `@Qualifier` → `@Primary` →  `Bean Name 매칭`의 순서로 단 하나의 최종 Bean을 선별한다.
  5. `DefaultListableBeanFactory#doResolveDependency()`
    Spring 의존성 해결의 실질적인 알고리즘은 `DefaultListableBeanFactory`의 `doResolveDependency()` 메서드 내부에서 구동된다.

'🍃SpringBoot' 카테고리의 다른 글

Spring IoC & DI 완전 정복: Chapter 8. Proxy와 Spring AOP  (1) 2026.07.22
Spring IoC & DI 완전 정복: Chapter 7. BeanPostProcessor와 Spring의 확장 포인트  (0) 2026.07.22
Spring IoC & DI 완전 정복: Chapter 5. BeanDefinition과 Bean 생성 과정  (0) 2026.07.22
Spring IoC & DI 완전 정복: Chapter 4. Spring Container는 무엇인가?  (0) 2026.07.22
Spring IoC & DI 완전 정복: Chapter 3: "제어를 프레임워크에게 넘긴다는 것은 무슨 의미일까?"  (0) 2026.07.22
'🍃SpringBoot' 카테고리의 다른 글
  • Spring IoC & DI 완전 정복: Chapter 8. Proxy와 Spring AOP
  • Spring IoC & DI 완전 정복: Chapter 7. BeanPostProcessor와 Spring의 확장 포인트
  • Spring IoC & DI 완전 정복: Chapter 5. BeanDefinition과 Bean 생성 과정
  • Spring IoC & DI 완전 정복: Chapter 4. Spring Container는 무엇인가?
limdaeil
limdaeil
limdaeil 님의 블로그 입니다.
  • limdaeil
    limdaeil
    limdaeil
  • 전체
    오늘
    어제
    • 분류 전체보기 (82) N
      • 💭Retrospective (21)
      • 🥕FrontEnd (0)
      • 🐬MySQL (1)
      • 🐍Python (5)
      • 🍃SpringBoot (33) N
      • ☕Java (1)
      • ♾️Devops (1)
      • 🌎Network (2)
      • 📚Read & 👨‍🏫Course (10)
      • Programmers (1)
      • 🧪Test (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Mockito
    책
    DI
    Python
    서평단
    IoC
    Spring
    MySQL
    나는리뷰어다
    spring boot
    한빛미디어
    optimistic rock
    junit
    Concurrency
    회고
    distributed lock
    redis
    jwt
    한빛아카데미
    gradle
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
limdaeil
Spring IoC & DI 완전 정복: Chapter 6. Spring은 의존성을 어떻게 해결할까?
상단으로

티스토리툴바