묵시적 DI(Implicit Dependency Injection)
스프링에서 묵시적 DI는 개발자가 “어떤 빈을 연결할지”를 직접 코드에 쓰지 않고,
스프링 컨테이너가 자동으로 빈을 찾아 주입해 주는 방식
1. @Component: 빈 후보(Bean Candidate) 등록
- 역할: 이 클래스는 “스프링이 관리할 수 있는 빈”이라는 표시만 한다.
- 실제 빈 등록은 @ComponentScan이 해당 패키지를 스캔해야 일어난다.
- 기본 빈 이름
- 클래스명 PascalCase → 메서드명 스타일 camelCase로 변환
- 예: MyService → myService
- @Component("customName") 속성으로 직접 지정 가능
- 클래스명 PascalCase → 메서드명 스타일 camelCase로 변환
// 빈 후보 표시
@Component
public class Hat { … } // 빈 이름: "hat"
@Component("watch")
public class Watch { … } // 빈 이름: "watch"
2. @Autowired: 자동 주입(Autowiring)
- 역할: 스프링이 타입(또는 이름)에 맞는 빈을 찾아서 주입해 준다.
- 적용 대상: 생성자, 필드, 세터, 일반 메서드 파라미터 전부 가능
- required 속성: required=true(기본)면 빈이 반드시 하나 있어야 한다.
- 빈이 여러 개일 때
- 타입 중복 오류 NoUniqueBeanDefinitionException 발생
- @Qualifier("beanName")를 추가해 이름으로 한정 주입 가능
- 생성자가 하나면 @Autowired 생략해도 자동으로 주입된다.
@Component
public class Bag {
private final Hat hat;
private Watch watch;
// 생성자 주입: final 필드 사용 가능, 필수 의존성에 권장
@Autowired
public Bag(Hat hat, @Qualifier("watch") Watch watch) {
this.hat = hat;
this.watch = watch;
}
// 선택적/추가 의존성: 세터 주입
@Autowired(required=false)
public void setSmartWatch(SmartWatch sw) {
this.smartWatch = sw;
}
}
3. @ComponentScan: 패키지 스캔
- 역할: @Component가 붙은 클래스를 스캔해서 실제 빈으로 등록한다.
- 사용 위치: 보통 @Configuration 클래스 위에 붙인다.
- 스캔 대상 설정:
- @ComponentScan("com.example.app") → 해당 패키지 및 하위 모두 스캔
- @ComponentScan(basePackages={"pkg1","pkg2"}) 처럼 배열로 지정 가능
@Configuration
@ComponentScan("com.ssfy.live.accessory")
public class AppConfig { }
4. 전체 흐름 예시
1. 클래스 선언
@Component class Hat { /* … */ }
@Component("watch") class Watch { /* … */ }
@Component class SmartWatch { /* … */ }
2. 의존성 주입 클래스
@Component
public class Bag {
private final Hat hat;
private final Watch watch;
private SmartWatch smartWatch;
// 필수 의존성: 생성자 주입
public Bag(Hat hat, Watch watch) {
this.hat = hat;
this.watch = watch;
}
// 선택 의존성: 세터 주입
@Autowired(required=false)
public void setSmartWatch(SmartWatch smartWatch) {
this.smartWatch = smartWatch;
}
}
3. 컨테이너 띄위기
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class);
Bag bag = ctx.getBean(Bag.class);
ctx.close();
5. 빈 조회 시 오류 처리
- 문제: @Autowired Watch watch; 선언만 했는데, Watch 타입 빈이 2개(watch, smartWatch) 있어 오류
NoUniqueBeanDefinitionException:
expected single matching bean but found 2: smartWatch,watch
- 해결: @Qualifier("watch")로 빈 이름을 지정한다.
@Autowired
public Bag(Hat hat,
@Qualifier("watch") Watch watch) {
…
}
6. 주입 방식별 장·단점 비교
방식 장점 단점 언제 사용?
생성자 주입 | final 필드 가능• 불변성 보장• 의존 누락 시 컴파일 단계 오류 | 파라미터 늘어나면 코드 길어짐 | 모든 필수 의존, 테스트·리팩토링 시 권장 |
세터 주입 | 선택적 의존성에 적합• 순환 참조 해결 가능 | 불변성 보장 안 됨• 누락 시 런타임 오류 | Optional 의존, 순환 참조가 불가피할 때 |
필드 주입 | 코드 가장 간결 | Mock 주입 어려움• 불변성 보장 안 됨 | 테스트 편의 목적, 아주 단순한 빈 한정 권장 |
7. 명시적 vs 묵시적 DI 비교
구분 명시적 DI 묵시적 DI
코드 작성 | JavaConfig(@Configuration+@Bean)나 XML에 명시적으로 빈 생성·주입 코드 작성 | @Component + @Autowired 애노테이션만으로 자동 연결 |
관심사 분리 | 비즈니스 로직 ↔ 빈 관리 로직 명확 분리 | 빈 관리 코드와 비즈니스 코드가 섞여 보일 수 있음 |
구조 파악 | 설정 클래스/파일만 보면 전체 의존관계 한눈에 파악 가능 | 코드 전체를 스캔해야 빈 구성을 유추해야 함 |
유연성 | 외부 라이브러리 빈 등록 자유, 세밀한 제어 가능 | 스캔 방식 제약 → 외부 빈 혼합 시 JavaConfig 필요할 수 있음 |
개발 속도 vs 안정성 | 안정성↑, 설정량↑ | 개발 속도↑, 설정 실수로 인한 런타임 오류 위험↑ |
팁: 보통은 묵시적 DI를 기본으로 쓰되,
- 외부 라이브러리 통합
- 특정 조건부 빈 등록
- 테스트용 목(Mocking) 빈 등록
같은 특수한 경우에만 JavaConfig(@Bean) 같은 명시적 DI를 보조적으로 사용하면 좋다
'SpringBoot' 카테고리의 다른 글
AOP(Aspect Oriented Programming) - (2) (0) | 2025.04.25 |
---|---|
AOP(Aspect Oriented Programming) - (1) (0) | 2025.04.25 |
DI(Dependency Injection) - (2) (0) | 2025.04.18 |
DI(Dependency Injection) - (1) (1) | 2025.04.18 |
Spring Framework (1) | 2025.04.16 |