본문 바로가기

Backend/Spring

[Spring] 의존관계 자동 주입 3) List & Map

728x90

 

 

목차

  • 의존관계 자동 주입 - 다양한 의존관계 주입 방법
  • 의존관계 자동 주입 - 옵션 처리
  • 의존관계 자동 주입 - 생성자 주입을 선택해라!
  • 의존관계 자동 주입 - 롬복과 최신 트랜드 (생성자 주입을 편리하게 사용하기)
  • 의존관계자동주입 - 조회빈이2개이상-문제
  • 의존관계 자동 주입 - @Autowired 필드 명, @Qualifier, @Primary
  • 의존관계 자동 주입 - 애노테이션 직접 만들기
  • 의존관계 자동 주입 - 조회한 빈이 모두 필요할 때, List, Map
  • 의존관계 자동 주입 - 자동, 수동의 올바른 실무 운영 기준

 

 

 

 

List & Map

 

 

조회한 빈이 모두 필요한 경우가 있다. 이런 경우는

  • List
  • Map

을 이용해서 해결한다

 

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

예를 들어 할인 서비스를 제공하는데, 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있는 경우가 있다.

 

코드 작성을 위해 test의 autowired 패키지 하위에 다음과 같은 클래스를 생성한다.

 

 

일단 이를 다 가져와 출력하는 코드부터 작성해보자.

 

AllBeanTest

package hello.core.autowired;

import hello.core.AutoAppConfig;
import hello.core.discount.DisountPolicy;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.List;
import java.util.Map;

public class AllBeanTest {

    @Test
    void findAllBean(){
        //의존관계 주입을 위한 AutoAppConfig class , 테스트를 위한 DiscountService class
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class);
    }

    static class DiscountService{//기존 orderService를 손대지 않기 위해 새로운 클래스로 테스

        private final Map<String, DisountPolicy> policyMap;
        private final List<DisountPolicy> policies;

        @Autowired
        public DiscountService(Map<String, DisountPolicy> policyMap, List<DisountPolicy> policies) {
            this.policyMap = policyMap;
            this.policies = policies;

            //의존관계 주입 확인을 위해 출력
            System.out.println("policyMap = " + policyMap);
            System.out.println("policies = " + policies);
        }



    }


}

 

다음과 같이 출력하면 결과가 

다음과 같이 DiscountPolicy인터페이스의 2개의 구현체가 @Component를 추가해서 bean으로 등록됐기 때문에,

fix, rate 두개 전부 가져와진 것을 볼 수 있다.

 

 

이를 응용하여, " 할인 서비스를 제공하는데, 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있는 경우가 있다. " 이를 구현해보자.

AllBeanTest

public class AllBeanTest {

    @Test
    void findAllBean(){
        //의존관계 주입을 위한 AutoAppConfig class , 테스트를 위한 DiscountService class
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class);

        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, "userA", Grade.VIP); //discountService사용을 위한 vip 멤버 생성
        int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy"); // fixDiscount적용 선택
        assertThat(discountService).isInstanceOf(DiscountService.class);
        assertThat(discountPrice).isEqualTo(1000);// fixDiscount 적용시 1000원할인 인지 확인


        int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy"); // rateDiscount적용 선택
        assertThat(rateDiscountPrice).isEqualTo(2000);//rate discount 적용시 2000원 할인인지 확인

    }

    static class DiscountService{//기존 orderService를 손대지 않기 위해 새로운 클래스로 테스트

        private final Map<String, DiscountPolicy> policyMap; //모든 discountpolicy를 주입 (fix, rate)
        private final List<DiscountPolicy> policies;

        @Autowired
        public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {//의존성 주입 Map
            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); //map에서 둘 중 하나의 로직을 선택
            return discountPolicy.discount(member,price);//해당 로직으로 할인 진행
        }
    }

}

테스트를 실행해보면 성공하는 것을 볼 수 있다.

Map을 이용하여, 스프링 빈으로 등록된 모든 discountPolicy를 저장해 둔다.

그 후 discount라는 할인 적용함수를 만들어서, 사용자가 선택한 할인 (고정/%)중 선택된 구현체 이름을 넘겨 해당 로직을 실행한다.

 

 

 

 

 

 

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

 

 

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

 

스프링, 스프링 부트의 등장으로 자동을 선호하는 추세

  • 일반적인 애플리케이션 로직 자동 스캔 지원
    • @Component 뿐 아니라 @Controller, @Service, @Repository처럼 계층에 맞추어 제공
  • 스프링 부트는 컴포넌트 스캔을 기본으로 사용
  • 편리하고 간단
    • 애플리케이션 구성 부분과 동작 부분을 명확하게 나누는 것이 이상적이지만(AppConfig작성), 편리함을 버릴 순 없음(@Component)
    • 관리할 빈이 많아지면서 AppConfig가 커지면 부담
  • 자동 빈 등록을 사용해도 OCP, DIP 지키기 가능 

 

 

수동 빈 등록을 언제 사용하면 좋을까?

  1. 기술 지원 객체
  2. 비즈니스 로직 중에서 다형성을 적극 활용할 때

 

 

기술 지원 객체

 

애플리케이션의 로직

  • 업무 로직 빈
    • 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리
    • 비즈니스 요구사항을 개발할 때 추가되거나 변경됨
  • 기술 지원 빈
    • 기술적인 문제나 공통 관심사(AOP) 처리시 사용
    • 데이터베이스 연결, 공통 로그 처리 등 업무 로직을 지원하기 위한 하부 기술/공통 기술

 

업무 로직은 숫자가 매우많고, 개발시 (컨트롤러, 서비스, 레포지토리 등) 유사 패턴이 존재 

문제가 발생해도 어느 곳에서 문제가 발생했는지 명확하게 파악 쉬움

→ 자동 기능 적극 활용 

 

기술 지원 로직은 수가 적고, 애플리케이션 전반에 걸쳐 광범위한 영향을 미침 

어디에 문제가 발생했는지, 어디가 문제인지, 심지어 잘 적용되고 있는지 조차 파악 어려움

→ 수동 빈 등록으로 명확하게 들어내기

 

 

 

애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 빈으로 등록해서

! 설정 정보에 바로 나타나게 하는 것이 유지보수 하기 좋다.

 

 

 

 

비즈니스 로직 중에서 다형성을 적극 활용할 때

 

이런 코드를 볼 때, 어떤 빈들의 주입될지, 각 빈의 이름은 무엇인지 보고 한눈에 알 수 없다. (특히 협업시)

찾기 위해서는 여러 코드를 살펴봐야 한다.

        private final Map<String, DiscountPolicy> policyMap; //모든 discountpolicy를 주입

 

 

이런 경우 수동 빈으로 등록하거나, 자동으로 등록시 특정 패키지에 같이 묶어두는게 좋다. (딱 보고 이해가 되어야함)

이 부분만 묶어서 별도로 수동 등록하면 다음 처럼 한눈에 파악이 가능하게 할 수 있다.

@Configuration
  public class DiscountPolicyConfig {
      @Bean
      public DiscountPolicy rateDiscountPolicy() {
          return new RateDiscountPolicy();
      }
      @Bean
      public DiscountPolicy fixDiscountPolicy() {
          return new FixDiscountPolicy();
      }
}

이 설정 정보만 봐도 한눈에 빈의 이름과, 어떤 빈이 주입될지 파악할 수 있다.

이를 자동 등록으로 하고싶다면, 파악하기 좋게 DiscountPolicy의 구현 빈만 따로 모아 특정 패키지(DiscountPolicy package)에 모아두는 것이 좋다. 

 

 

참고 : 스프랑과 스프링부트가 자동으로 등록하는 수많은 빈들은 예외 

예) DataSource와 같은 데이터베이스 연결에 사용하는 기술 지원 로직까지 내부에서 자동으로 등록하는데, 이런 부분은 메뉴얼을 잘 참고해서 스프링 부트가 의도한 대로 편리하게 사용하면 된다. 

반면에 스프링 부트가 아니라 내가 직접 기술 지원 객체를 스프링 빈으로 등록한다면 수동으로 등록해서 명확하게 들어내는 것이 좋다.

제공하는 기술들을 잘 찾아보고 사용하자.

 

 

<정리>

  • 편리한 자동 기능을 기본으로 사용하자
  • 직접 등록하는 기술 지원 객체는 수동 등록
  • 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보자

 

728x90