@Autowired는 Type으로 스프링 빈을 조회한다. 따라서 applicationContext.getBean(DiscountPolicy.class)와 유사하게 동작한다. 그런데 DiscountPolicy 인터페이스의 구현클래스가 FixDiscountPolicy, RateDiscountPolicy 이렇게 두개가 있다면@Component public class FixDiscountPolicy implements DiscountPolicy {} // @Component public class RateDiscountPolicy implements DiscountPolicy {}
NoUniqueBeanDefinitionException 예외가 발생한다.
@Autowired는 타입 매칭을 시도하고 여러 개의 빈이 있으면 필드 이름으로 빈 이름을 매칭한다. 따라서 필드명을 빈 이름으로 변경하면 된다.
@Component가 있는 빈 등록 위치에 @Qualifier를 이용하여 이름을 적어준다.
@Component @Qualifier("mainDiscountPolicy") public class RateDiscountPolicy implements DiscountPolicy { ... } // @Component @Qualifier("fixDiscountPolicy") public class FixDiscountPolicy implements DiscountPolicy { ... }
@Qualifier는 문자열로 @Qualifier를 찾기 때문에 만약 @Qualifier("mmainDiscountPolicy") 이렇게 오타를 내면 컴파일 에러는 나지 않고 오류를 찾기 힘들다. 따라서 아래와 같이 애너테이션을 만들어서 사용하는게 일반적이다.
**무분별한 애너테이션 재정의는 유지보수에 방해가 된다.
@Primary는 우선순위를 정하는 방법으로 @Autowired 시에 여러 빈이 매칭되면 @Primary가 있는 빈이 우선권을 가진다.
@Component @Primary public class RateDiscountPolicy implements DiscountPolicy { ... } //우선권 가짐 // @Component public class FixDiscountPolicy implements DiscountPolicy { ... }
위 세 가지 방법을 잘 살펴보면 세 방법 모두 필드에 DiscountPolicy라는 인터페이스에 의존하고 있기 때문에 DIP위반은 아니다. 하지만, 만약 RateDiscountPolicy에서 FixDiscountPolicy로 변경하게 된다면 코드의 수정이 일어나므로 OCP를 위반한다. SOLID원칙은 지키려고 노력해야 하는 것. 하지만 지키지 못할 상황도 있는 것이다.
위 세 가지 방법 중에 @Primary, @Qualifier를 적절히 섞어서 사용하는 것이 좋다.
어떤 메인으로 동작하는 것을 컨트롤 하는 빈이 있고, 코드에서 특별한 상황에 사용하는 빈이 있으면, 메인으로 사용되는 빈에 @Primary를 붙이고, 서브로 사용되는 빈에 @Qualifier("빈 이름")을 붙여서 명시적으로 사용해주면 된다. 참고로 @Primary보다 @Qualifier가 우선순위가 높다.