당니의 개발자 스토리

@Autowired 필드 명, @Qualifier, @Primary 본문

스프링/스프링 핵심 원리 - 기본편

@Autowired 필드 명, @Qualifier, @Primary

clainy 2024. 1. 24. 23:30

@Autowired 필드 명, @Qualifier, @Primary

이번 시간에는 여러 개의 빈이 선택이 될 때, 어떻게 해결하는지에 대한 3가지 방법을 하나씩 알아보겠습니다.

조회 대상 빈이 2개 이상일 때 해결 방법이 첫 번째는 @Autowired에 필드 명을 매칭시키는 방법이 있어요.

두 번째는 @Qualifier@Qualifier끼리 매칭시키는 게 있어요.

세 번째는 @Primary 라는 걸 사용하는 게 있습니다.


먼저, @Autowired필드 명을 매칭시킨다는 말은

@Autowired가 좀 특이한 기능이 있어요. @Autowired처음에 타입 매칭을 시도해요.

그런데 빈이 두 개잖아요? 여러 빈이 있으면, 필드 이름이나 파라미터 이름으로 빈 이름을 추가 매칭해요.

만약에 필드 주입이라고 합시다.

이럴 때 지금 DiscountPolicy가 FixDiscountPolicy도 있고, RateDiscountPolicy도 있죠. 타입 조회로 하면 2개가 조회될 겁니다. 그러면, @Autowired가

필드 주입인 경우 필드명이나,

생성자 주입인 경우, 파라미터 이름을 봐요.

따라서, 필드 명을 이렇게 변경해두면, 이 이름을 가지고 두 개의 빈 중에서 빈 이름이랑 필드 명이랑 똑같은 애를 찾아와요.

그렇게 해서 타입이 똑같은 애들이 있으면, 필드 명과 매칭된 애를 대상으로 딱 찍어 가지고 하나만 가져올 수 있습니다. 어차피 필드 명과 똑같은 애가 2명이 있다고 해도, 오류로 반환하거나 overriding 되기 때문에 문제가 없죠.

아까 돌려보면 오류가 났었는데, basicScan()AutoAppConfig를 쓰면서 컴포넌트 스캔으로 DiscountPolicy 타입의 빈 2개를 등록해서 그래요.

이제 이렇게 바꾸고 다시 돌려보면,

실패하던 테스트가 성공합니다. 이게 @Autowired가 가진 특별한 기능입니다.

만약 필드 주입으로 했어도, @Autowired가 필드 명을 읽기 때문하나만 찾아올 겁니다.

참고로 타입으로 조회할 때 하나만 있잖아요. 그러면 빈 이름과 상관없이 그냥 성공해요. 여러 개가 조회됐을 때 빈 이름을 매칭하는 시나리오가 들어가는 거예요.

그런데 test 폴더 전체를 테스트 돌리면 오류가 발생합니다.

우리가 이름을 rateDiscountPolicy로 바꿨잖아요. appConfig.xmlrateDiscountPolicy로 바꾸고 다시 돌리면 성공합니다.

그래서 @Autowired 매칭 정리를 하면,

첫 번째 Type으로 매칭을 한다. Type이 하나면 빈 이름 아무것도 안 보고, 그냥 하나니까 무조건 들어와서 주입을 해줍니다.

근데 Type으로 매칭 결과가 2개 이상이다. 라고 하면, 먼저 스프링은 어떻게 매칭한다 했죠?

본인과 같은 타입이거나 또는 그 타입의 자식들을 다 끌고 온다고 했죠.

그래서 타입 매칭의 결과가 2개 이상일 때는 필드 명, 파라미터 명으로 빈 이름을 매칭해서 같은 애가 있으면 걔를 가져다가 주입을 해줍니다.


두 번쨰 방법은 @Qualifier 라는 굉장히 특별한 애노테이션을 붙여주는 거예요.

@Qualifier추가 구분자라고 보셔야 돼요. 주입시 추가적인 방법을 제공하는 거지, 빈 이름 자체를 변경하는 게 아니에요. 구분할 수 있는 추가적인 옵션을 하나 제공하는 거에요. 어떻게 사용하냐면,

예를 들어서 '아 RateDiscountPolicy가 앞으로 우리가 자주 쓸 main이야!' 라고 해서 mainDiscountPolicy라는 이름을 @Qualifier를 사용해서 딱 지정을 했어요.

이렇게 특별한 별명 같은 걸 부여해놓고,

FixDiscountPolicy에다가는 fixDiscountPolicy 라고 @Qualifier 애노테이션을 줄게요.

이렇게 해놓으면, OrderServiceImpl에서 파라미터에다가 @Qualifier를 넣을 수 있습니다.

그리고 다시 테스트를 돌리면, 위의 @Qualifier를 보고, 같은 내용@Qualifier를 찾아서 주입을 해줍니다.

테스트가 다 성공했네요.

참고로 수정자필드 주입에서도 @Qualifier를 쓸 수 있습니다.

@Qualifier로 주입할 때, @Qualifier("mainDiscountPolicy") 이렇게 추가 구분자를 넣었는데,

만약 mainDiscountPolicy를 못찾으면 어떻게 될까요? 그러면 @Qualifier도 mainDiscountPolicy 라는 이름스프링 빈을 추가로 찾습니다. @Autowired랑 비슷하죠. 이 과정이 두 번째입니다.

그렇게 해서, 만약 mainDiscountPolicy라는 이름의 스프링 빈이 있으면, 그 스프링 빈을 찾아옵니다.

하지만 경험상 @Qualifier는 @Qualifier를 찾는 용도로만 사용하는게 명확하고 좋습니다. 개발에서 헷갈리는 건 좋지 않아요. @Qualifier가 은근 헷갈린단 말이에요.

그러니까 @Qualifier는 @Qualifier끼리만 같이 쓴다라고 딱 기억하시고, 그렇게만 운영하시는게 훨씬 낫습니다.

그리고 빈을 직접 등록할 때에도 @Qualifier를 동일하게 사용할 수 있습니다.

그래서 정리를 해보면,

@Qualifier끼리 매칭한다. 그런데 매칭되는 @Qualifier가 없으면 두 번째로 빈 이름을 매칭해본다.

그래도 안 되면, NoSuchBeanDefinitionException 라는 예외가 터진다입니다.


세 번째는 @Primary인데요. 은근 편하고 자주 사용합니다.

@Primary는 편하긴 한데, 한계점은 있어요. @Primary는 우선순위를 지정하는데요.

@Autowired 시에 여러 개의 빈이 매칭되면, 이 @Primary 라는 애노테이션이 지정 되어있으면 얘가 그냥 선택이 됩니다.

한번 해보겠습니다. @Qualifier를 빼주고,

cmd + option + B 하면, 구현체들을 보여줍니다. FixDiscountPolicy랑 RateDiscountPolicy 들어가서 @Qualifier를 빼주고, '우린 RateDiscountPolicy먼저 선택되게 할 거야!' 라고 하시면,

RateDiscountPolicy에다가 @Primary를 넣어주시면 됩니다. 다시 테스트를 돌려보면,

테스트가 성공합니다.

지금 DiscountPolicy으로 조회하면 여러 개의 빈이 조회되었는데, RateDiscountPolicy에 보니까 @Primary가 붙어있어요. 그러면 다른 거 다 무시하고, 우선적으로 RateDiscountPolicy만 우선 순위가 최상위로 잡히면서, RateDiscountPolicy가 의존관계 주입이 됩니다.

생각보다 많이 쓰입니다. @Primary가 왜 좋냐면, @Qualifier은 지저분하게 붙여야 되잖아요.

그래서 어떤 경우에 많이 쓰이냐면, 예를 들어서 어떤 데이터베이스 커넥션과 관련된 정보인데, 메인 데이터베이스가 있고 보조 데이터베이스가 있다고 할게요. 메인 데이터베이스의 로직이 90%고 보조 데이터베이스는 어쩌다 5%, 10%도 안되는 로직이 있어요. 그러면 메인 데이터베이스를 가져오는 커넥션을 할 때 @Qualifier를 해주고, 보조 데이터베이스를 가져올 때도 @Qualifier 해주면 되게 귀찮잖아요. 두 번 다 @Qualifier를 해줘야되는 거잖아요.

그래서 메인 데이터베이스 커넥션을 가져오는 코드에는 @Primary를 걸어주는 거에요.

그러면 의존관계 주입을 해도 되게 심플해지고, 룰은 팀에서 정하면 되겠죠. "메인 DB는 그냥 @Primary로 쓰자. 대신에 보조 DB는 거의 쓰지 않으니까 이때는 @Qualifier나, 직접 이름을 지정하는 식으로 매칭해서 가지고 오자" 이렇게 룰을 정할 수 있습니다.

그래서 여기까지 보면 @Primary 와 @Qualifier 중에 어떤 것을 사용하면 좋을지 고민을 할 수 있는데요. @Qualifier 의 단점은 주입 받을 때 모든 코드에 @Qualifier 를 붙여주어야 한다는 점입니다.

반면에 @Primary 를 사용하면, @Qualifier 를 붙일 필요가 없죠.


그래서 아까 든 예시를 다시 보면,

다시한번 읽어보시면 됩니다.

@Primary 는 기본 값처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작하죠. @Primary 랑 @Qualifier 가 둘 다 있으면 뭐가 높은 우선순위를 가질까요?

이런 경우는 항상 자세한 것이 우선권을 가져갑니다. 스프링은 항상 자동보다는 수동으로, 넓은 선택 범위보다는 좁은 선택 범위에 있는 게 우선순위가 높아요.

따라서 여기서도 @Primary 보다는 @Qualifier가 굉장히 세세하게 다 이름까지 지정해주는 거니까 우선순위가 더 높습니다.


다음 시간에는 Annotation을 한번 직접 만들어 보겠습니다.