1. 의존성이란 무엇인가
의존성 (Dependency)이란 A 코드 없이는 B 코드가 정상적으로 동작할 수 없는 관계를 의미한다.
- B가 A를 사용하고 있고
- A가 없으면 B는 실행 자체가 불가능하다면
→ B는 A에 의존하고 있다.
Spring에서 DI (Dependency Injection, 의존성 주입)는 이 의존 관계를 코드 내부에서 직접 생성 (new) 하지 않고
외부 (Spring Container)가 대신 주입해주는 구조를 만드는 것이 목적이다.
2. 어제 코드에서의 문제 상황 (test05)
상황 요약
- IPhone이 볼륨 업 / 다운 기능을 수행
- 실제 소리 조절은 Watch 객체가 담당
- IPhone 내부에서 매번 new Watch()를 해서 사용
발생한 문제
- NullPointerException
- 멤버 변수로 watch를 선언해 놓고
- 생성자에서 초기화하지 않음
- 메서드 안에서 쓰기 직전에 new로 할당
- 객체를 계속 사고 버리는 구조
- 볼륨 업 → 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 |