이 포스트는 인프런 사이트에 김영한 - 스프링 핵심원리 강의를 보고 정리한 포스터입니다.
스프링 핵심 원리 - 기본편 강의 | 김영한 - 인프런
김영한 | , 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢 수강 전 확인해주세요! 본 강의는 자바 스프링 완전 정복 시리즈의 두 번째 강의입니다. 우아한형제들 최연
www.inflearn.com
스프링에서는 같은 타입의 빈이 여러 개 존재할 때, 이를 어떻게 해결할 것인지에 대한 다양한 방법을 제공합니다.
이번 포스트에서는 빈이 2개 이상일 때 발생하는 문제와 해결 방법(@Autowired 필드 명, @Qualifier, @Primary), 애노테이션을 직접 만들어 해결하는 방법, 조회한 빈이 모두 필요할 때 List와 Map을 사용하는 방법, 그리고 자동과 수동 등록의 올바른 실무 운영 기준에 대해 자세히 정리하겠습니다.
이전 포스트 의존관계 자동 주입 1편을 보고 오면 도움됩니다.
의존관계 자동 주입 1편
이 포스트는 인프런 사이트에 김영한 - 스프링 핵심원리 강의를 보고 정리한 포스터입니다.https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8 스프
uplifted.tistory.com
🔆 조회 대상 빈이 2개 이상일 때 발생하는 문제
@Autowired는 기본적으로 타입을 기준으로 빈을 주입합니다.
그런데 같은 타입의 빈이 2개 이상 존재할 경우 NoUniqueBeanDefinitionException 오류가 발생합니다.
✅ 문제 상황
@Autowired
private DiscountPolicy discountPolicy;
위 코드에서 DiscountPolicy 타입을 가진 빈이 2개 이상 존재하면 다음과 같은 예외가 발생합니다.
NoUniqueBeanDefinitionException: No qualifying bean of type 'DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy, rateDiscountPolicy
즉, DiscountPolicy 타입의 빈이 fixDiscountPolicy, rateDiscountPolicy 두 개가 존재하기 때문에 어떤 빈을 주입해야 할지 알 수 없어서 오류가 발생합니다.
🔆 조회 대상 빈이 2개 이상일 때 해결 방법
🌀 (1) @Autowired 필드명 매칭
@Autowired는 기본적으로 타입으로 빈을 찾지만, 필드명과 동일한 빈 이름이 있으면 해당 빈을 주입합니다.
✅ 해결 방법 예제
@Autowired
private DiscountPolicy rateDiscountPolicy; // 빈 이름과 필드명을 일치시킴
위처럼 필드명을 rateDiscountPolicy로 변경하면 스프링이 자동으로 rateDiscountPolicy 빈을 주입합니다.
✔ 우선 타입으로 매칭하고, 타입이 여러 개이면 필드명과 빈 이름을 매칭하는 방식으로 해결됩니다.
🎈 @Autowired 매칭 정리
1. 타입 매칭
2. 타입 매칭의 결과가 2개 이상일 때 필드 명, 파라미터 명으로 빈 이름 매칭
🌀 (2) @Qualifier 사용 (구체적인 빈 이름 지정)
@Qualifier를 사용하면 특정 빈을 직접 선택하여 주입할 수 있습니다.
빈을 등록할 때 @Qualifier를 붙여주고, 주입할 때도 같은 @Qualifier를 사용해야 합니다.
✅ 빈 등록 시
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("subDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
✅ 주입 시
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
✔ @Qualifier("mainDiscountPolicy")를 통해 RateDiscountPolicy가 주입됩니다.
🎈 @Qualifier 매칭 정리
1. @Qualifier 매칭
2. 빈 이름 매칭
3. NoSuchBeanDefinitionException 예외 발생
🌀 (3) @Primary 사용 (우선순위 지정)
@Primary는 같은 타입의 여러 빈이 있을 때 우선적으로 주입할 빈을 지정하는 방법입니다.
✅ @Primary를 사용한 빈 등록
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Primary가 적용된 RateDiscountPolicy가 우선적으로 주입됩니다.
@Qualifier와 @Primary가 함께 사용될 경우 @Qualifier가 더 높은 우선순위를 가집니다.
✔ 자주 사용하는 빈은 @Primary를 적용하고, 특정 빈을 명시적으로 선택해야 할 경우 @Qualifier를 사용하면 좋습니다.
🔆 애노테이션을 직접 만들어서 해결하기
@Qualifier("mainDiscountPolicy")와 같이 문자열을 사용하는 방식은 컴파일 시점에 오류를 잡아내기 어렵다는 단점이 있습니다.
이를 해결하기 위해 애노테이션을 직접 만들어 사용할 수 있습니다.
✅ 커스텀 애노테이션 생성
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {}
✅ 적용 예제
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
✔ @MainDiscountPolicy를 사용하면 가독성이 높아지고, 컴파일 타임 체크가 가능해집니다.
🔆 조회한 빈이 모두 필요할 때 (List, Map 사용하기)
의도적으로 특정 타입의 모든 빈을 조회해야 하는 경우가 있습니다.
대표적인 예 : 전략 패턴(Strategy Pattern)을 사용할 때
✅ 예제: 모든 DiscountPolicy 빈 조회
@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;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
public int discount(String discountCode, Member member, int price) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
`Map<String, DiscountPolicy>` : map의 키에 스프링 빈의 이름을 넣어주고, 그 값으로 `DiscountPolicy` 타입으로 조회한 모든 스프링 빈을 담아준다.
`List<DiscountPolicy>` : `DiscountPolicy` 타입으로 조회한 모든 스프링 빈을 담아준다.
만약 해당하는 타입의 스프링 빈이 없으면, 빈 컬렉션이나 Map을 주입한다.
✅ 실행 결과
policyMap = {fixDiscountPolicy=FixDiscountPolicy@1a2b3c, rateDiscountPolicy=RateDiscountPolicy@4d5e6f}
policies = [FixDiscountPolicy@1a2b3c, RateDiscountPolicy@4d5e6f]
✔ policyMap을 사용하면 빈 이름으로 원하는 빈을 선택해서 사용할 수 있습니다.
✔ List<DiscountPolicy>를 사용하면 모든 빈을 순서대로 조회할 수 있습니다.
🔆 자동등록 vs 수동 등록 - 실무 운영 기준
스프링에서는 대부분의 빈을 자동 등록(@ComponentScan)하는 것이 일반적입니다.
하지만, 일부 상황에서는 수동 등록(@Bean)을 고려해야 합니다.
🔸 자동 등록이 적합한 경우
✔ 업무 로직 관련 빈 (예: @Service, @Repository, @Controller)
✔ 패턴이 일정한 빈 (예: @Component로 등록하는 일반 빈)
🔸수동 등록이 적합한 경우
✔ 애플리케이션 전반에 영향을 미치는 기술 지원 빈 (예: 데이터베이스 연결, 공통 로깅, 트랜잭션 관리)
✔ 외부 라이브러리를 활용하는 경우
✔ 의존관계가 명확해야 하는 경우 (예: 전략 패턴에서 특정 구현체를 명확하게 지정해야 할 때)
✅ 정리
등록 방식 | 사용 사례 |
자동 등록 (@ComponentScan) | 일반적인 서비스, 레포지토리, 컨트롤러 |
수동 등록 (@Bean) | 기술 지원 빈, 외부 라이브러리, 특정 구현체가 필요한 경우 |
📌 결론
✔ 같은 타입의 빈이 여러 개 있을 때는 @Qualifier, @Primary, @Autowired 필드 명을 활용해 해결할 수 있습니다.
✔ 애노테이션을 직접 만들어 사용하면 가독성과 유지보수성이 향상됩니다.
✔ 빈이 여러 개 필요할 때는 List와 Map을 활용하면 편리합니다.
✔ 실무에서는 자동 등록을 기본으로 하되, 기술 지원 빈은 수동 등록하는 것이 좋습니다.
📌 스프링의 의존관계 자동 주입을 올바르게 활용하면 코드의 유지보수성과 확장성을 크게 향상시킬 수 있습니다. 🚀
'개발일지 > Spring' 카테고리의 다른 글
빈 스코프 (0) | 2025.02.24 |
---|---|
빈 생명주기 콜백 (0) | 2025.02.20 |
의존관계 자동 주입 1편 (0) | 2025.02.19 |
컴포넌트 스캔 (0) | 2025.02.18 |
싱글톤 패턴, 싱글톤 컨테이너 (0) | 2025.02.18 |