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

    조회한 빈이 모두 필요할 때? 


    이런 경우를 한번 생각해보자. 쇼핑몰에서 고객이 물건을 살 때 할인을 해준다고 가정하자. 이 때 할인 정책은 정액 할인 정책과 정율 할인 정책이 있다. 이 때, 할인 정책을 고객이 선택할 수 있는 경우를 가정해보자. 어떤 고객은 정율 할인 정책을 원할 것이고, 어떤 고객은 정액 할인 정책을 원할 것이다.

    위 상황은 특정 할인 정책으로만 의존관계 주입이 필요한 것이 아니라, 입력이 오는대로 동적으로 의존관계 주입이 필요한 상황이다. 이럴 때는 조회된 2개 이상의 빈을 모두 불러와서, 그 중에 필요한 빈을 동적으로 사용해야한다. 이를 위해서 조회된 빈을 List나 Map 형식으로 가져오고, 여기서 필요한 빈으로 잠시 객체를 만든다. 그리고 그 객체를 활용해 할인 정책을 제공하는 방식으로 접근할 수 있다. 

     

    STEP1. 조회된 빈을 담을 수 있는 또 다른 설정 클래스를 만든다.

     static class DiscountService {
    
            private final Map<String, DiscountPolicy> discountPolicyMap;
            private final List<DiscountPolicy> discountPolicyList;
    
            // AutoWired를 활용한 주입.
            public DiscountService(Map<String, DiscountPolicy> discountPolicyMap, List<DiscountPolicy> discountPolicyList) {
                this.discountPolicyMap = discountPolicyMap;
                this.discountPolicyList = discountPolicyList;
            }
    
            public int discount(Member member, int price, String discountCode) {
                DiscountPolicy discountPolicy = discountPolicyMap.get(discountCode);
    
                System.out.println("discountCode = " + discountCode);
                System.out.println("discountPolicy = " + discountPolicy);
                return discountPolicy.discount(member, "itemA", price);
            }
    }

    먼저 위 클래스를 만든다. 위 클래스는 필드 변수로 Map, List 형태의 discountPolicyMap과 discountPolicyList라는 변수를 가진다. 생성자를 하나 만들고 @Autowired로 넣어줄 수 있는데, 이 때 Map, List 형태라면 스프링이 Map, List의 매개변수에 대응되는 모든 스프링빈을 찾아서 주입해주는 특별한 기능이 있다고 한다. (코드 레벨에서는 사실 이해가 안됨) 

     

    첨부 : @Autowired가 붙은 메서드의 파라미터로 Map<String, BeanType>이 온다면 스프링은 BeanType에 해당하는 빈들을 찾아서 Map으로 의존관계를 주입해준다고 한다. 이는 스프링에서 제공하는 기능 중 하나라고 한다. 아래 링크에 설명 내용이 있다. Core Technologies (spring.io)

     

    위의 코드가 수행이 되면, DiscountPolicy의 빈 객체를 주입받은 List와 Map을 가진 싱글톤 빈 객체인 DiscountService가 생성된다. 

    STEP2. static class안에 동적 계산 메서드 만들기

            public int discount(Member member, int price, String discountCode) {
                DiscountPolicy discountPolicy = discountPolicyMap.get(discountCode);
    
                System.out.println("discountCode = " + discountCode);
                System.out.println("discountPolicy = " + discountPolicy);
                return discountPolicy.discount(member, "itemA", price);
            }

    싱글톤 빈 객체 DiscountService 안에 내부 동적 메서드를 구현한다. DiscountService는 스프링 컨테이너가 아닌 빈 객체이기 때문에, 빈 객체 내부의 필드 변수로 동작하는 메서드를 만들어야 한다. 내부의 DiscountPolicy에는 이미 스프링 빈 객체의 참조값을 가지고 있다.

    이 때, discountCode로 원하는 정책을 입력받고, 그 정책의 이름을 key로 해서 DiscountPolicy 객체 참조값을 읽어온다. 그리고 그 객체에 대한 discount 메서드를 사용해 서, 실제 할인 값을 return 해준다.

    STEP3. Main Test 코드 짜기

     @Test
        void test1(){
    
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,
                    DiscountService.class);
            DiscountService discountService = ac.getBean(DiscountService.class);
    
    
            Member member = new Member(1L, "nameA", Grade.VIP);
            int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
    
            assertThat(discountService).isInstanceOf(DiscountService.class);
            assertThat(discountPrice).isEqualTo(1000);
    
    
            Member memberA = new Member(1L, "nameB", Grade.VIP);
            int discountPrice1 = discountService.discount(memberA, 20000, "rateDiscountPolicy");
            assertThat(discountPrice1).isEqualTo(2000);
    
        }

    AnnotationConfigApplicationContext에 AutoAppConfig.class, DiscountService.class를 빈으로 등록해준다. AutoAppConfig.class는 설정 정보에 따라 @ComponentScan으로 이런저런 빈들을 등록해준다. DiscountService.class는 빈을 생성할 때, 생성자에 의해서 스프링 컨테이너에 있는 DiscountPolicy가 자동적으로 주입이 되어 빈이 등록된다. 

    이후 스프링 컨테이너에 등록된 DiscountService 빈을 객체로 가지고 와서, 사용하는 형태로 구현이 가능하다.

     

    주의 사항 

    Spring 컨테이너에 AutoAppConfig.class를 넣지 않으면 @ComponentScan이 일어나지 않는다. 따라서 FixDiscountPolicy, RateDiscountPolicy 같은 빈 객체들이 스프링 컨테이너에 빈 등록되지 않기 때문에 원하는대로 동작하지 않는다.

    댓글

    Designed by JB FACTORY