이전 편에서는 스프링이 제공하는 다양한 의존관계 자동 주입 방식에 대해 살펴봤다. 이번 편에서는 그보다 더 나아가, 실무에서 자주 쓰이는 고급 자동 주입 기능들을 정리해본다.
빈이 여러 개일 때 발생하는 문제
@Autowired
private DiscountPolicy discountPolicy;
위 코드처럼 타입으로 자동 주입할 때, 해당 타입의 빈이 2개 이상 등록되어 있으면 예외가 발생한다.
스프링은 내부적으로 ac.getBean(DiscountPolicy.class)와 같은 방식으로 동작하기 때문에, 동일 타입의 빈이 여러 개면 어떤 빈을 선택해야 할지 알 수 없다.
해결 방법 1: @Primary 우선순위 지정
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {
// ...
}
- 같은 타입의 빈이 여러 개일 때, @Primary가 붙은 빈이 자동 주입의 우선순위가 됨
- 단, @Qualifier가 명시적으로 붙어 있으면 무시됨
해결 방법 2: @Qualifier 이름 지정 주입
@Component
@Qualifier("mainDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {
// ...
}
@Autowired
public OrderServiceImpl(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
- @Qualifier는 해당 이름과 정확히 일치하는 빈을 찾는다.
- 주입 대상이 없으면 NoSuchBeanDefinitionException 발생
- @Qualifier는 빈 이름이 아닌 qualifier 값으로 주입을 유도하는 것이므로, 이름 일치를 강제하는 것과는 다름
실무에서 애노테이션 직접 만들어 사용하기
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
- 커스텀 애노테이션으로 의미를 명확하게 만들 수 있음
- 컴파일 타임에 체크할 수 있어 오류 방지에 도움
- 단, 웬만하면 스프링에서 제공하는 기능으로 충분하므로 남용 금지
조회한 모든 빈이 필요한 경우: Map, List 주입
@Component
public class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
- 모든 빈을 한 번에 주입받고 싶을 때 유용
- Map의 key는 빈 이름, value는 빈 인스턴스
- 전략 패턴(Strategy Pattern)을 스프링에서 자연스럽게 구현할 수 있음
실무 운영 기준 - 자동 vs 수동 빈 등록
결론: 자동 등록을 기본으로 하자.
- 자동 빈 등록은 컴포넌트 스캔만으로 대부분의 기능을 구성할 수 있어서 생산성이 뛰어남
- 수동 등록은 설정 정보가 커지고 관리 포인트가 많아지므로 필요할 때만 사용
자동 등록이 더 나은 이유
- 유지보수성: 변경 시 한 군데만 고치면 됨
- 유연성: OCP, DIP 같은 설계 원칙을 지키면서 유연한 구조 가능
마무리: 실무에서 어떤 방식이 가장 좋은가?
- 무조건 생성자 주입 + @RequiredArgsConstructor + @Primary 또는 @Qualifier 조합
- 테스트 필요 시에는 setter 주입 허용
- 필드 주입은 절대 사용하지 말 것
- 전략 패턴을 쓰는 구조에서는 Map<String, Bean> 주입 방식 적극 활용
'Spring' 카테고리의 다른 글
[Spring 완전 정복 시리즈] 21편 - 빈 스코프 완전 정복 (1): 싱글톤과 프로토타입 (0) | 2025.08.01 |
---|---|
[Spring 완전 정복 시리즈] 20편 - 빈 생명주기 콜백과 초기화 전략 정리 (1) | 2025.07.31 |
[Spring 완전 정복 시리즈] 18편 - 의존관계 자동 주입 방식 완전 정리 (0) | 2025.07.30 |
[Spring 완전 정복 시리즈] 17편 - 다양한 의존관계 자동 주입 방식 (1) | 2025.07.29 |
[Spring 완전 정복 시리즈] 16편 - 컴포넌트 스캔 심화 (0) | 2025.07.29 |