이전 글 보러가기😉

2022.09.25 - [Spring] - 스프링 핵심 원리 - 기본편 | 7. 의존관계 자동 주입 1

 

스프링 핵심 원리 - 기본편 | 7. 의존관계 자동 주입 1

김영한님의 [스프링 핵심 원리 - 기본편]을 보고 작성한 글입니다:) 1. 다양한 의존관계 주입 방법 생성자 주입 setter 주입 필드 주입 일반 메서드 주입 생성자 주입 생성자를 통해서 의존 관계를

yiseul-coding.tistory.com

 


 

1. 의존관계 자동 주입시 빈이 2개 이상 조회된다면?!

@Autowired는 타입(Type)으로 조회한다. 타입으로 조회 시 만약 빈이 2개 이상 일 때 어떤 문제가 발생할까?

바로 NoUniqueBeanDefinitionException 오류가 발생한다. 

 

이때 하위 타입으로 지정할 수도 있지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다.

그리고 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 때는 해결이 안된다.

 

이럴 때, 의존관계 자동 주입에서 해결하는 방법은 3가지가 있다. 하나씩 살펴보자

  • @Autowired 필드 명 매칭
  • @Qualifier끼리 매칭 -> 못찾으면 빈 이름 매칭
  • @Primary 사용

 

1. @Autowired 필드 명 매칭

  1. @Autowired는 먼저 타입 매칭을 시도한다.
  2. 이때 2개 이상의 빈이 있으면 필드 명, 파라미터 명으로 빈 이름 매칭한다. (1번이 실패했을 때 추가 동작)

 

2. @Qualifier 사용

  • 빈 등록시 @Qualifier를 붙여준다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
  • 의존관계 주입 시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
  • 생성자 주입, setter 주입, 필드 주입 모두 사용 가능하다.
@Autowired
public OrderServiceImpl(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
 	this.discountPolicy = discountPolicy;
}

만약 @Qualifier로 주입할 때, @Qualifier("mainDiscountPolicy")를 못찾으면 어떻게 될까?

그때는 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다.

 

@Qualifier 정리

  1. @Qualifier끼리 매칭
  2. 1번 실패 시 빈 이름 매칭
  3. 2번 실패 시 NoSuchBeanDefinitionException 예외 발생

 

3. @Primary 사용

@Primary는 우선순위는 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 @Primary가 붙은 빈이 우선권을 가진다.

 

@Primary, @Qualifier 우선순위

스프링은 자동보다는 수동이, 넓은 범위의 선택권보다는 좁은 범위의 선택권이 우선순위가 높다.

따라서 @Qualifier가 우선순위가 높다.

 

@Primary, @Qualifier 활용

2개 이상의 빈을 사용해야 할 때, 코드에서 자주 사용하는 스프링 빈은 @Primary를 적용해서 기본값처럼 동작하게 한다. 덜 사용하는 스프링 빈은 @Qualifier를 지정해서 명시적으로 획득하는 방식을 사용한다.

 


 

2. 조회한 빈이 모두 필요할 때, List, Map

의도적으로 해당 타입의 스프링 빈이 모두 필요한 경우도 있다.

예를 들면 할인 서비스를 제공하는데, 클라이언트가 할인의 종류를 선택할 수 있다고 가정해보자.

@Component
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);
    }
}

로직 분석

  • DiscountService는 Map으로 모든 DiscountPolicy 타입의 빈들을 주입받는다.
  • discount() 메서드는 discountCode로 스프링 빈 이름이 넘어오면 map에서 해당 이름을 가진 스프링 빈을 찾아서 실행한다.

주입 분석

  • Map<String, DiscountPolicy>: map의 키에 스프링 빈의 이름을 넣어주고, 그 값으로 DIscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.
  • List<DiscountPolicy>: DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.
  • 만약 해당하는 타입의 스프링 빈이 없으면, 빈 컬렉션이나 Map을 주입한다.

참고. 위의 코드에서 생성자는 딱 1개이므로 @Autowired를 생략해서 의존관계가 자동 주입된다.

 


 

3. 자동, 수동의 올바른 실무 운영 기준

편리한 자동 기능을 기본으로 사용하자!

 

그렇다면 수동 등록할 때는 언제일까?

  • 직접 기술 지원 객체를 등록할 때
    • 애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서 설정 정보에 바로 보이게 하는 것이 유지보수 하기 좋다.
  • 비지니스 로직 중에서 다형성을 적극 활용할 때
    • 위에서 조회한 빈이 모두 필요할 때, List, Map을 보면, DiscountService는 의존관계 자동 주입으로 DiscountPolicy 타입의 모든 빈을 주입받는다. 이런 경우 DiscountPolicy 의 구현 빈들만 따로 수동 등록하면, 설정 정보만 봐도 한 눈에 어떤 빈들이 주입될지 파악할 수 있다.

+ Recent posts