본문 바로가기

Language & Framework/Spring

스프링 빈을 Map, List 등 컬렉션 프레임워크로 호출해서 사용하기

 

기본 개념을 포스팅하는 건 별로 의미가 없다는 생각이 들어 놓은 지 좀 되었는데.

이번에는 내가 이해한 게 맞는지 혼란이 와서 나중에 다시 읽어보기 위해 작성하게 되었다.

 

만약 DiscountPolicy라는 인터페이스를 구현한 FixDiscoutPolicy와 VariableDiscountPolicy라는 클래스가 있을 때.

클라이언트가 고정 할인과 비율 할인을 자유롭게 선택할 수 있도록 하려면 어떻게 해야할까?

각 코드를 따로 작성하는 방법도 있겠지만 다형성을 활용하여 하나의 로직으로 모두 해결할 수 있다.

 

public class AllBeanTest {
    @Test
    void findAllBean() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
        
        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, Grade.VIP, "userA");
        int discountPrice = discountService.discount(member,10000,"fixDiscountPolicy");
        Assertions.assertThat(discountService).isInstanceOf(DiscountService.class);
        Assertions.assertThat(discountPrice).isEqualTo(1000);

        int variableDiscount = discountService.discount(member, 20000, "variableDiscountPolicy");
        Assertions.assertThat(variableDiscount).isEqualTo(2000);
    }
    
    static class DiscountService {
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;

        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;
            System.out.println("policyMap" + policyMap);
            System.out.println("policies = " + policies);
        }

        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            return discountPolicy.discount(member, price);
        }
    }
}

 

내가 헷갈렸던 부분들에 대한 정리.

1. @Component도 없고 @Autowired도 없고 대체 어디서 빈을 주입 받고 있는 거지?

=> AnnotiationConfigApplicationContext를 생성할 때 인자로 받은 클래스들을 자동으로 스프링 빈 컨테이너에 등록해준다.

그리고 innerClass인 DiscountService에서 해당 클래스를 검색하면 자동으로 해당 타입(Map, List)에 맞는 컬렉션 자료들을 찾아서 반환하는 것이다.

 

 

2. Map<String, DiscountPolicy>에서 String에 들어가는 게 뭐지?

=> 불러온 맵을 표준 출력으로 확인해보면 이런 결과가 나온다.

fixDiscountPolicy=helloProject.core.discount.FixDiscountPolicy@6da00fb9, 

variableDiscountPolicy=helloProject.core.discount.VariableDiscountPolicy@a202ccb

 

스프링 컨테이너에 빈은 이름과 객체로 저장되는데, Map으로 스프링 빈에 저장된 클래스를 탐색하면 해당 이름과 객체를 Map 형태로 주입해준다. 그 키와 값을 그대로 받은 것이다.

그리고 빈의 이름은 "클래스명의 제일 앞 글자를 소문자로 변경한 형태"로 저장되기 때문에 해당 규칙에 따라 원하는 빈을 호출해주면 된다.

 

 

 

로직 정리.

 

1. 

    static class DiscountService {
        private final Map<String, DiscountPolicy> policyMap;
        // Map<String, DiscountPolicy>과 같은 타입의 스프링 빈을 찾아 주입
        private final List<DiscountPolicy> policies; // 의미 없는 코드 무시할 것

        @Autowired //생성자가 하나면 생략 가능하지만 일단 적었다.
        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies; // 의미 없는 코드
            System.out.println("policyMap" + policyMap); // 스프링 빈의 이름과 객체 주소가 출력됨
            System.out.println("policies = " + policies); // 의미 없는 코드
        }

        public int discount(Member member, int price, String discountCode) {
            DiscountPolicy discountPolicy = policyMap.get(discountCode);
            //discountCode 파라미터에 빈의 이름만 적어주면 해당 빈 객체를 주입하여 선택권을 준다.
            //discountPolicy.discount(member, price)는 기존에 존재하던 메서드이다.
            return discountPolicy.discount(member, price);
        }

2.

    void findAllBean() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
        // AnnotationConfigApplicatonContext는 말 그대로 "스프링 컨테이너" (여기까지는 순수한 자바 코드)
        // 스프링이 이 코드를 참고하여 AutoConfig.class, DiscountService.class를 스프링 빈으로 등록한다.
        
        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, Grade.VIP, "userA");
        int discountPrice = discountService.discount(member,10000,"fixDiscountPolicy");
        // 빈 이름을 인자로 넣고 테스트 성공
        Assertions.assertThat(discountService).isInstanceOf(DiscountService.class);
        Assertions.assertThat(discountPrice).isEqualTo(1000);

        int variableDiscount = discountService.discount(member, 20000, "variableDiscountPolicy");
        Assertions.assertThat(variableDiscount).isEqualTo(2000);
    }

 

 

 

역시 헷갈릴 때는 한 번 기록하는 게 제일 좋은 방법인 것 같다.

정리하느라 진도를 못 나가기는 했지만.. 이해하지 못하고 넘어가는 것보다는 낫겠지 🥲