수업 복습

Spring DI (의존성 주입)

_김영인 2026. 1. 14. 17:33

 

1. 의존성이란 무엇인가

의존성 (Dependency)이란 A 코드 없이는 B 코드가 정상적으로 동작할 수 없는 관계를 의미한다.

  • B가 A를 사용하고 있고
  • A가 없으면 B는 실행 자체가 불가능하다면
    → B는 A에 의존하고 있다.

Spring에서 DI (Dependency Injection, 의존성 주입)는 이 의존 관계를 코드 내부에서 직접 생성 (new) 하지 않고

외부 (Spring Container)가 대신 주입해주는 구조를 만드는 것이 목적이다.


 

2. 어제 코드에서의 문제 상황 (test05)

 

상황 요약

  • IPhone이 볼륨 업 / 다운 기능을 수행
  • 실제 소리 조절은 Watch 객체가 담당
  • IPhone 내부에서 매번 new Watch()를 해서 사용

 

발생한 문제

  1. NullPointerException
    • 멤버 변수로 watch를 선언해 놓고
    • 생성자에서 초기화하지 않음
    • 메서드 안에서 쓰기 직전에 new로 할당
  2. 객체를 계속 사고 버리는 구조
    • 볼륨 업 → Watch 하나 생성해서 쓰고 버림
    • 볼륨 다운 → 또 하나 생성해서 쓰고 버림

 

구조적으로 무엇이 문제였나

 

이 구조는 실제로는 다음과 같았다.

  • IPhone이 Controller 역할
  • Watch가 DAO 역할
  • Controller가 DAO를 직접 new 해서 사용

의존 객체를 직접 생성하고 직접 관리하는 구조였다.

 

실행은 된다.
하지만 구조적으로는 최악에 가까운 코드다.


 

3. 문제의 핵심 원인

 

핵심 원인은 한 줄로 정리된다.

 

멤버 변수가 생겼는데 멤버 변수 초기화를 담당하는 생성자가 없었다.

  • 멤버 변수는 클래스가 생성될 때 함께 준비돼야 한다
  • 그런데 메서드 안에서 new를 하고 있었다
  • 이건 “쓰기 직전에 급하게 만드는 구조”

멤버 변수가 생긴 순간
바로 생성자에서 초기화했어야 했다


 

4. 해결 방향: 생성자 주입 (Constructor Injection)

 

핵심 아이디어

  • IPhone이 Watch를 직접 만들지 않는다
  • IPhone을 만들 때 Watch를 외부에서 주입받는다

 

코드 구조

public class IPhone {

    private Watch watch;

    public IPhone(Watch watch) {
        this.watch = watch;
        System.out.println("아이폰 객체 생성");
    }
}
 

이 코드의 의미는 명확하다.

  • “아이폰을 만들려면 워치가 반드시 필요하다”
  • 의존 관계가 생성자 시그니처에 명시적으로 드러난다

이 순간, 코드가 클린 코드가 된다.


 

5. 생성자 주입이 만들어내는 의존성 관계

 

생성자 주입의 가장 큰 특징은 다음이다.

  • Watch가 없으면 IPhone 자체를 만들 수 없다
  • 즉, 의존성이 강제된다

 

이는 설계적으로 매우 중요한 신호다.

“이 객체는 이 의존성이 없으면 존재할 수 없다”


 

6. 그런데 왜 바로 안 돌아갔을까?

 

생성자 주입으로 코드 구조는 좋아졌지만 Spring 환경에서는 두 가지 문제가 동시에 발생했다.

 

문제 1. Spring Container는 기본 생성자를 사용한다

  • XML 설정 기준
  • 아무 설정이 없으면 기본 생성자로 객체를 생성
  • 생성자 인자가 있는 생성자는 사용하지 않음

 

문제 2. Watch 객체가 Spring Container에 없다

  • IPhone 생성은 Spring이 담당
  • 그런데 생성자 인자로 넣을 Watch Bean이 없음 

“아이폰은 내가 만들게.
근데 워치는 네가 준비해줘야지?”

 

이 상태였다.


 

7. XML에서 생성자 주입 설정

 

이를 해결하려면 XML에서 명시적으로 알려줘야 한다.

  • 생성자 인자가 있는 생성자를 사용할 것
  • 그 인자로 어떤 Bean을 주입할 것인지

이때 사용하는 것이 constructor-arg

  • 의미 그대로 생성자 인자

Spring Container는

  • 타입을 보고
  • 알아서 맞는 객체를 주입한다

단,

  • 자동 형변환은 해주지 않음
  • 타입은 정확히 일치해야 함

 

8. Setter 주입과 생성자 주입의 차이

 

생성자 주입

  • 의존 객체가 없으면 객체 생성 불가
  • 필수 의존성 표현에 적합
  • 구조적으로 안전

 

Setter 주입

  • 의존 객체가 없어도 객체 생성 가능
  • 나중에 주입 가능
  • 멤버 변수 이름 기준으로 주입

 

예시로 보면

  • IPhone → 생성자 주입
  • GalaxyPhone → Setter 주입

 

9. XML 설정의 현실적인 문제

  • XML은 위에서 아래로 읽힌다
  • 설정이 꼬이면 에러 추적이 어렵다
  • Container가 여러 개면 객체 생성 순서가 매번 달라질 수 있다

 

그래서 실무에서는 다음 습관이 중요하다.

  • 기본 생성자에 로그를 찍어둔다
  • XML 로딩 시 객체 생성 순서를 확인한다

 

10. XML이 너무 어렵다 → Annotation으로 전환

 

그래서 등장한 것이 Annotation 기반 설정이다.

필요한 설정

  • context 네임스페이스 추가
  • component-scan 활성화

 

이 설정이 의미하는 바는 다음이다.

“이 패키지 아래에서
컴포넌트 어노테이션이 붙은 클래스를 찾아서
Bean으로 자동 등록해라”


 

11. @Component와 계층 어노테이션

 

Spring은 역할별로 어노테이션을 제공한다.

  • @Component
    → 가장 기본적인 Bean 등록
  • @Repository
    → DAO 계층 (Component의 자식)
  • @Service
    → 비즈니스 로직 계층

기능은 거의 동일하지만 의도를 명확하게 드러내는 것이 목적이다.


 

12. @Autowired – DI 해줘라는 의미

 

@Autowired의 역할은 단순하다.

  • “이 타입의 Bean을 찾아서 주입해라”

 

중요한 점은 이것이다.

  • 객체 생성을 해주는 게 아니다
  • 이미 컨테이너에 있는 Bean을 주입해줄 뿐이다

 

13. Autowired의 가장 중요한 함정: 모호성 문제

 

상황

  • Watch 인터페이스
  • AppleWatch 구현체 하나만 있을 때

→ 문제 없음
→ 타입으로 하나뿐이니 자동 주입

 

문제 상황

  • SmartWatch 구현체 추가
  • Watch 타입 Bean이 2개가 됨

→ Spring은 어떤 걸 넣어야 할지 모름
모호성 에러 발생


 

14. 모호성 해결 방법 두 가지

 

방법 1. 하나를 제거하거나 주석 처리

  • 가장 빠른 테스트용 방법
  • 컴파일 비용 없음
  • 실무에서도 디버깅 시 자주 사용

 

방법 2. @Qualifier 사용

  • Bean에 이름 부여
  • Autowired 시 이름으로 지정

 

이 방식은

  • 명확하지만
  • 코드 변경 → 재컴파일 필요

그래서 실무에서는 처음부터 Bean 이름을 명확히 짓는 문화를 가진 회사가 많다.


 

 

  • DI의 본질은 객체 생성과 사용의 분리
  • 생성자 주입은 의존성을 설계로 드러낸다
  • Setter 주입은 선택적 의존성에 적합하다
  • @Component는 <bean>을 대체한다
  • @Autowired는 “주입해줘”라는 의미일 뿐 생성은 아니다
  • Bean이 여러 개면 반드시 모호성 문제가 발생한다

 

'수업 복습' 카테고리의 다른 글

콘솔 회원·게시판 프로그램  (0) 2026.01.15
객체지향 → IoC → Spring Container 흐름  (0) 2026.01.14
다형성 복습 및 과제  (0) 2026.01.13
Spring Framework  (1) 2026.01.13
커스텀 태그 복습  (0) 2025.12.01