당니의 개발자 스토리

조회한 빈이 모두 필요할 때, List, Map 본문

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

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

clainy 2024. 1. 25. 20:10

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

이번 시간에는 조회한 빈이 모두 필요할 때, 리스트으로 한번에 조회하는 방법에 대해서 알아보겠습니다.

실무에서 쓸만한 예제로 말씀을 드리겠습니다.

의도적으로 정말 해당 타입의 스프링 빈이 다 필요한 경우가 있어요.

예를 들어서 우리가 할인과 관련된 어떤 서비스를 제공하는데, 클라이언트가 할인의 종류를 선택할 수 있는 거예요. 클라이언트가 "저는 비율로 할인해주세요", "저는 고정으로 할인해주세요" 라고 하면서 클라이언트가 선택할 수 있어요.

스프링을 사용하면, 소위 말하는 전략 패턴을 매우 간단하게 구현할 수가 있습니다. 제가 이거는 코드를 설명해 드릴게요.


test의 autowired에다가 AllBeanTest를 만들겠습니다. 타입으로 조회된 모든 빈을 찾는 테스트죠.

여기까지 작성해두고, 새로운 서비스를 만들게요. DiscountService 라는 클래스를 만들겠습니다. 기존의 OrderService를 손 대면 너무 복잡해지니까, DiscountService를 만듭니다.

DiscountServiceMap으로 StringDiscountPolicy를 다 받습니다. 그리고 필드가 final이니까 생성자로 주입받습니다.

그 전에, 리스트는 테스트 삼아서 받아볼게요. List로도 받을 수 있다는 걸 보여주기 위해서.

생성자까지 만들었습니다. 물론 이렇게 생성자 안 만들고 @RequiredArgsConstructor 해도 됩니다. 그런데 지금은 생성자 안에서 출력을 할 거여서 이렇게 해준 겁니다.

일단 이렇게 하면, DiscountPolicy 타입의 클래스를 다 가져오겠죠. 되는지 먼저 확인해보겠습니다. 그러기 위해선,

일단 DiscountService.class스프링 빈으로 등록하고요. 테스트를 실행해보면, DiscountService.class가 스프링 빈으로 등록이 되면서 생성자가 호출되겠죠. 현재 생성자가 1개이니까 자동으로 @Autowired 돼서 의존관계 자동 주입이 될 겁니다.

그런데 막상 출력을 해보니까 아무것도 없어요. 왜냐면, 지금

현재 이것만 스프링 빈으로 등록한 거잖아요. DiscountPolicy를 땡겨와야겠죠.

이전에 했던 AutoAppConfig를 땡겨와서, AutoAppConfig를 빈으로 등록하고 DiscountService도 등록합니다.

그러면 DiscountService자동 의존관계 주입이 될 때, AutoAppConfig에선 컴포넌트 스캔을 하죠.

cmd + B 해서 AutoAppConfig로 넘어가보면, AutoAppConfig은 컴포넌트 스캔을 합니다.

그러면서, FixDiscountPolicyRateDiscountPolicy 둘 다 스프링 빈에 등록이 되기 때문에 다시 테스트를 돌려보면,

policyMap = {fixDiscountPolicy=hello.core.discount.FixDiscountPolicy@6c2f1700, rateDiscountPolicy=hello.core.discount.RateDiscountPolicy@350b3a17}
policies = [hello.core.discount.FixDiscountPolicy@6c2f1700, hello.core.discount.RateDiscountPolicy@350b3a17]

자동 의존관계 주입이 되어서 출력 결과가 나옵니다. policyMap은 Key가 fixDiscountPolicy 이고, Value가 hello.core.discount.FixDiscountPolicy@6c2f1700스프링 빈입니다.

또 Key가 rateDiscountPolicy이고, Value가hello.core.discount.RateDiscountPolicy@350b3a17스프링 빈입니다.

List는 hello.core.discount.FixDiscountPolicy@6c2f1700로 스프링 빈 인스턴스가 바로 탁탁 들어옵니다. List 같은 경우는 빈 이름이 없죠. 왜냐면, MapKey, Value가 있는데 ListValue만 있잖아요.

그 다음 기가막힌 걸 보여드리겠습니다.

이제 비즈니스 로직을 하나 만들어볼게요. 이건 TDD(테스트 주도 개발)로 만들어보겠습니다.

먼저 ac에서 DiscountService를 꺼내서 할인 서비스를 하나 변수로 만들 겁니다. 여기 안에는 DiscountPolicy 타입의 빈들이 담겨있겠죠.

그런데 이전에 DiscountService 하려면, Member가 필요하므로, 하나 만들겠습니다.

member가 할인 정책을 선택을 하는 거죠. member를 만든 다음에, DiscountServicediscount()를 불러서 할인된 금액이 얼만지 보는 거예요.

그 member가 VIP인지 보고, 금액 넘기고, 어떤 할인정책을 쓸 건지 넘겨줄 겁니다. 그리고 return 값은 할인 금액이니까

이렇게 하고, option + enter 해서,

dicount 메서드를 만들어줍시다.

일단 이렇게 하고, Assertions를 해줍시다.

먼저, discountService가 잘 받아졌는지부터 확인할게요.

그 다음에 할인 금액을 검증해야죠.

이렇게 하고 돌려 보면 당연히 실패하겠죠. discount 메서드 return 값을 무조건 0으로 설정했기 때문입니다.

일단 파라미터 이름을 이렇게 변경해줍니다. 할인 코드를 빈 이름이랑 매칭시키는 거예요.

그러면, 아까 Map에다가 빈 이름(String)이랑 할인정책(DiscountPolicy)을 넣었으니까 discountCode를 이용해서 그 이름에 알맞는 할인 정책을 꺼내야겠죠.

우리가 아까 "fixDisocuntPolicy"를 넣었기 때문에 fixDisocuntPolicy가 들어갈 거예요. 그 다음에 할인 금액은

이렇게 해서 return 하면 나옵니다.

테스트를 돌리면 성공하죠. 아직 믿음이 부족 하니까 하나 더 해볼게요.

테스트를 돌리면 또 성공했습니다.

그래서 이런 다형성 코드를 완성했습니다.


이제 로직을 간단하게 분석해보겠습니다.

먼저, DiscountService는 모든 DiscountPolicy를 생성자에서 Map으로 주입받습니다.

여기서는 생성자 하나이기 때문에 @Autowired를 생략해도 됩니다.

이 때, fixDiscountPolicy랑 rateDiscountPolicy가 이 policyMap에 다 주입이 됩니다.

그리고 discount 메서드는

discountCode로 "fixDiscountPolicy"가 내려오면 policyMap에서 key로 찾아서, 현재 맵에 등록되어 있는 Key가 fixDiscountPolicy인 스프링 빈을 꺼내서 discountPolicy.discount(member, price); 로직을 호출해줍니다. 물론 나는 인터페이스를 알고 있죠.

물론 RateDiscountPolicy가 넘어오면 rateDiscountPolicy 라는 이름의 스프링 빈을 찾아서 실행을 해줍니다.

주입은 계속 분석을 해봤구요.

이거를 그냥 스프링 없이 하려면 되게 귀찮거든요. 이렇게 하면, 정말 간단하게 스프링이 빈에 등록된 애들을 자동으로 쫙 주입해가지고,

이렇게 다형성을 활용해서 유연한 전략 패턴을 발휘하게 된 거죠.

이렇게 해서, 조회한 빈이 모두 필요할 때 List와 Map을 이용해서 받는 걸 해봤구요. 어느 경우에 활용하면 좋은지 아시겠죠.

실제로 사용하면 되게 편리하게 쓸 수 있는 경우가 진짜 많아요. 우리가 해본 예제처럼 동적으로 빈을 선택해야 될 때, 으로 딱 받아 가지고 해버리면 정말 편리하게 다형성 코드를 유지하면서 빈을 사용할 수가 있습니다.

참고로 스프링 컨테이너생성자에 클래스 정보를 받습니다. 그래서 생성자 파라미터 클래스 정보를 넘기면, 해당 클래스가 스프링 빈으로 자동 등록됩니다.

정리하면 스프링 컨테이너를 생성하면서, 해당 컨테이너에 동시에 AutoAppConfig , DiscountService 를 스프링 빈으로 자동 등록하는 겁니다.


다음 시간에는 자동으로 빈을 등록하는 것과 수동으로 빈을 등록하는 것에 대한 올바른 실무 운영 기준에 대해서 말씀드리겠습니다.