-
[Spring] 동일 타입 빈 조회Backend Dev/Spring Framework 2022. 3. 16. 18:34728x90
@Autowired는 타입으로 빈을 조회하는데 동일 타입에서 선택된 빈이 2개 이상일 때 문제가 발생한다. 이는 스프링 빈을 수동 등록으로도 문제 해결이 가능하지만 의존 관계 자동 주입으로 해결하는 방법이 있다.
@Autowired : 필드 명 매칭@Autowired는 처음에 타입 매칭을 시도하고 동일 타입에 여러 빈이 있을 경우 필드 이름이나 파라미터 이름으로 빈 이름을 추가 매칭한다. 필드명 매칭은 먼저 타입 매칭을 시도 후 그 결과에 여러 빈이 있을 때 추가로 동작하는 기능이다.
// 기존 코드 @Autowired private DiscountPolicy discountPolicy // 필드명을 빈 이름으로 변경 @Autowired private DiscountPolicy rateDiscountPolicy
@Qulifier 사용
@Qualifier는 추가 구분자를 붙여주는 방법으로 주입시 추가적인 방법을 제공해주며 빈 이름을 변경하는 것은 아니다.@Component @Qualifier("mainDiscountPolicy") public class RateDiscountPolicy implements DiscountPolicy { } @Component @Qualifier("fixDiscountPolicy") public class FixDiscountPolicy implements DiscountPolicy { }
빈 등록시 @Qualifier를 붙여주고 주입시 @Qualifier에 등록한 이름을 적어준다. @Qualifier는 @Qualifier를 찾는 용도로만 사용하고, 직접 빈 등록시에도 @Qualifier를 동일하게 사용할 수 있다. 지정한 이름의 빈 이름을 추가 매칭 했는데도 찾지 못한다면 예외가 발생한다.@Autowired public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) { this.memberRepository = memberRepository; this.discountPolicy = discountPolicy; }
@Primary 사용
우선순위를 정하는 방법으로 생성자 주입에서 @Autowired 시 여러 빈이 매칭되면 @Primary가 붙은 클래스가 우선권을 가진다.@Component @Primary public class RateDiscountPolicy implements DiscountPolicy { } @Component public class FixDiscountPolicy implements DiscountPolicy { }
@Qualifier는 주입받을 때 모든 코드에 추가적으로 @Qualifier를 붙여줘야 하지만 @Primary는 붙일 필요가 없어 편리하다는 것을 확인할 수 있다.
메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 우선순위를 적용해 조회하는 곳에서 @Qualifier 지정없이 편리하게 조회하고 서브 데이터베이스의 커넥션을 획득할 때는 @Qualifier를 지정해 명시적으로 주입해줌으로써 코드의 깔끔함을 유지할 수 있다.@Primary는 기본값처럼 동작하나 @Qualifier는 상세하게 동작하므로 스프링에서는 자동보다 수동의 선택권이 우선순위가 더 높기에 @Qualifier의 우선권이 더 높다.
조회한 빈이 모두 필요한 경우라면...? (List, Map 활용)
가령, 할인 서비스를 제공하는데 클라이언트가 할인의 종류를 선택하게 할 때 해당 타입의 스프링 빈이 모두 필요한 경우에 해당한다. 이는 스프링을 사용해 전략 패턴을 간단히 구현할 수 있다.public class AllBeanTest { @Test void findAllBean() { ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class); DiscountService discountService = ac.getBean(DiscountService.class); Member member = new Member(1L, "userA", Grade.VIP); int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy"); assertThat(discountService).isInstanceOf(DiscountService.class); assertThat(discountPrice).isEqualTo(1000); } static class DiscountService { private final Map<String, DiscountPolicy> policyMap; private final List<DiscountPolicy> policies; 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); } } }
테스트 코드에 나와있듯이 스프링 컨테이너를 생성하면서, 동시에 AutoAppConfig , DiscountService 를 스프링 빈으로 자동 등록한다.
DiscountService 에서는 Map과 List를 통해 모든 DiscountPolicy(rate, fix) 가 생성자 주입되고 클라이언트에서 쓰고자하는 할인 정책에 따라 discount()에서 discountCode를 읽어들여 Map에 있는 스프링 빈을 찾아 실행한다. 참고로, 생성자 주입을 할 때 해당 타입의 스프링 빈이 존재하지 않는다면 빈 컬렉션 또는 Map이 주입된다.
자동, 수동의 실무 운영 기준
자동 기능을 기본으로 사용하는 것이 좋다.
수동으로 빈을 등록하거나 의존관계 주입시 관리할 빈이 많아질 경우 설정 정보 관리가 어렵고, 자동 빈 등록을 사용해도 OCP, DIP 원칙은 지킬 수 있다.따라서, 직접 빈을 등록하거나 관계를 주입해주는 거 보다 @Component, @Autowired로 컴포넌트 스캔과 자동 주입을 사용하는 게 좋다.
수동 빈이 사용되는 경우
기술적인 문제나 공통 관심사를 처리할 때(AOP, DB 연결, 공통 로그 처리) 주로 사용된다. 업무 로직의 경우 쉬운 문제 파악과 유사 패턴이 있으므로 자동 기능을 사용하는 게 낫고, 기술 지원 로직은 로직의 수도 상대적으로 적고 실제 드러나는 부분이 아니기에 적용이 되고 있는지 모르는 경우가 많아 수동 빈 등록을 사용해 명확하게 파악하는 게 좋다.또한, 앱에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록하여 설정 정보를 한 눈에 파악하게 하는 것이 좋다.
비즈니스 로직 중 다형성을 활용할 때(위와 같이 여러 개의 빈이 동일 타입에 의존관계가 주입된 경우), 자동 기능이 사용된다면 어떤 빈들이 주입될지 파악하기 어렵기에 수동 빈으로 등록하거나 자동으로 사용할 시 특정 패키지에 묶어놓는 것이 좋다.본 내용은 인프런 핵심원리 - 기본편 김영한님의 강의를 참고하였습니다.
728x90'Backend Dev > Spring Framework' 카테고리의 다른 글
[Spring] JPA vs JDBC (0) 2022.12.20 [Spring] 빈 생명주기 콜백 (0) 2022.03.19 [Spring] 의존 관계 주입의 다양한 방식 (0) 2022.03.16 [Spring] 컴포넌트 스캔과 의존관계 자동 주입 (0) 2022.03.15 [Spring] 싱글톤 컨테이너 (0) 2022.03.08