[Spring] Autowired 사용시 주의 (Feat. Custom Annotation)

2022. 12. 1. 19:55·JVM/Spring

 

한 타입(Interface)에 대해 여러 Bean 이 등록되어 있으면 어떻게 해결할 것인가?

 

 

Autowired 동작 순서

  1. Type Matching
  2. Type Matching 결과가 2개 이상인 경우 필드명이나 파라미터 명으로 빈 이름 매칭.
public class OrderServiceImpl implements OrderService{
    // 구현체가 아닌 Interface (역할) 에만 의존해야한다. => DIP 원칙 준수
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    // Type Matching 시도
    // 타입 매칭 결과가 2개 이상인 경우 필드명 혹은 파라미터 명 (여기서는 rateDiscountPolicy)으로 이름 매칭.
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = rateDiscountPolicy;
    }
}

 

 

@Qualifier annotation 사용하기

class, field 모두 사용가능하다.

alias를 지정하는 기능으로 이해하면된다. 특정 alias 를 찾지 못하면 해당 alias 이름으로 스프링빈을 추가로 찾는다.

@Qualifer 는 @Qaulifier 를 찾는데만 사용하는 것이 좋다.


작동 순서

  1. @Qualifer 끼리만 먼저 매칭 시도
  2. 빈 이름으로 매칭 시도
  3. 둘다 못찾으면 `NoSuchBeanDefinitionException` 예외 발생

예제

fixDiscount.java

@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {

    private final int discountFixAmount = 1_000;
}

rateDiscount.java

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
    private final int discountPercent = 10;
}

OrderServiceImple.java

public class OrderServiceImpl implements OrderService{
    // 구현체가 아닌 Interface (역할) 에만 의존해야한다. => DIP 원칙 준수
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    // @Qualifier 로 mainDiscountPolicy 찾기
    public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }

 

@Primary 사용

When to use

어떤 클래스 or 필드를 우선권을 가지게 할 것인지를 지정하려할 때

주로 사용하는 클래스나 필드가 존재할 때

ex) 메인 DB 와 보조 DB 가 있는 경우 로직의 90%가 메인 DB 일 때는 메인 DB Connection Code 에 @Primary 어노테이션을 미리 붙여놓는다.

 

RateDiscount.java

@Component
// 우선권 부여
@Primary
public class RateDiscountPolicy implements DiscountPolicy {
    private final int discountPercent = 10;
}

 

OrderServiceImple.java

public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    // discountPolicy 에 @Primary 가 걸려있는 rateDiscount 를 DI 한다.
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

 

 

 

@Primary vs @Qualifier 둘다 걸려있다면?

우선권은 더 좁은 범위, 상세한 값이 가진다.

스프링은 자동보다는 수동, 범용적인것 보다 특정 기능을 하는 것에 우선순위를 둔다. 여기서는 @Qaulifier 가 더 높은 우선순위를 가진다.

 

 

 

Annotation 을 수동으로 만들어 사용하기

 

When to use?

@Qaulifier 를 떡칠하면서 쓰고있는데, 아쉽게도 annotation '문자열'에 대해서는 직접 실행해보지 않고는 오류를 잡을 수가 없다.

Compile time 에 IDE 의 도움을 받으며 유지보수하기 좋은 방식으로 개발할 순 없을까?

 

직접 Annotation 을 재정의한다.
// @Qaulifier 를 복사한다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
    // 단순 문자열로 명시한 것보다
    // IDE Compile 타임에 에러를 미리 잡을 수 있다.
}

 

RateDiscount.java

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {
    private final int discountPercent = 10;
}

Annotation 'MainDiscountPolicy' 를 붙여 `@Qualifier("mainDiscountPolicy")` 를 붙인 것과 동일한 효과를 볼 수 있다.

 

OrderServiceImpl.java

@Service
public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    // Compile time 에 에러를 잡을 수 있다.
    // IDE 단에서 이 어노테이션을 사용한 위치를 추적할 수 있다.
    public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

 

 

⚠️ 보통의 경우는 기본적으로 Spring 에서 제공하는 기본 Annotation 으로 해결이 가능하다.
명확한 이유가 존재할 때만 Annotation 을 재정의하여 사용하도록 하자.

 

 

 

Map, List 를 통한 DI, 동적 bean 사용

When to use?

동적으로 여러 빈을 조회, 사용해야할 때

Map 과 List를 통해 여러 빈을 동적으로 매핑시켜 다형성을 지키며 테스트 코드 작성 가능.

전략 메소드 패턴이라고도 한다.

 

 

AllBeanTest.java

각각의 Map, List 로 매핑시켜 DI.

public class AllBeanTest {

    static class DiscountService {
    // AppConfig.java 를 통해 쫙 DI 가 주입된다.
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policyList;

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

		// key: Bean name (class name as camelCase)
        public int getDiscountPrice(Member member, int price, String discountCode) {
            var discountPolicy = policyMap.get(discountCode);
            return discountPolicy.getDiscountPrice(member, price);
        }
    }

    @Test
    void findAllBean() {
        // Component Scan 을 하면서 fixDiscountPolicy, rateDiscountPolicy (camelCase) 모두 읽어들인다.
        // 모두 list, map 에 각각 DI 된다.
        var ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
        DiscountService discountService = ac.getBean(DiscountService.class);
        var member = new Member(1L, "userA", Grade.VIP);
        // Component Scan 시 자동으로 camelCase 로 Spring Bean 을 Container 에 등록.
        var fixDiscountPrice = discountService.getDiscountPrice(member, 10_000, "fixDiscountPolicy");
        assertThat(fixDiscountPrice).isEqualTo(1_000);

        var rateDiscountPrice = discountService.getDiscountPrice(member, 20_000, "rateDiscountPolicy");
        assertThat(rateDiscountPrice).isEqualTo(2_000);
    }
}
저작자표시 (새창열림)

'JVM > Spring' 카테고리의 다른 글

[SpringBoot] Naming methods in each layer  (0) 2023.02.09
[Spring] Library 살펴보기 및 스프링 환경설정  (0) 2022.12.12
[Spring] Bean Scope  (0) 2022.12.07
[Spring] 빈 생명주기 콜백  (0) 2022.12.06
[Spring] Component Scan, DI autowired  (0) 2022.11.30
'JVM/Spring' 카테고리의 다른 글
  • [Spring] Library 살펴보기 및 스프링 환경설정
  • [Spring] Bean Scope
  • [Spring] 빈 생명주기 콜백
  • [Spring] Component Scan, DI autowired
M_Falcon
M_Falcon
  • M_Falcon
    Falcon
    M_Falcon
  • 전체
    오늘
    어제
    • 분류 전체보기 (432)
      • Web (16)
        • Nodejs (14)
        • Javascript (23)
        • FrontEnd (4)
      • DataBase (39)
        • Fundamental (1)
        • Redis (4)
        • PostgreSQL (10)
        • NoSQL (4)
        • MySQL (9)
        • MSSQL (3)
        • Error (4)
      • Algorithm (79)
        • Algorithm (문제풀이) (56)
        • Algorithm (이론) (23)
      • JVM (65)
        • Spring (13)
        • JPA (5)
        • Kotlin (13)
        • Java (24)
        • Error (7)
      • 기타 (70)
        • Kafka (3)
        • Kubernetes (3)
        • Docker (13)
        • git (19)
        • 잡동사니 (27)
      • 재테크 (11)
        • 세무 (4)
        • 투자 (3)
        • 보험 (0)
      • BlockChain (2)
        • BitCoin (0)
      • C (32)
        • C (10)
        • C++ (17)
        • Error (3)
      • Low Level (8)
        • OS (3)
        • 시스템 보안 (5)
      • 네트워크 (3)
      • LINUX (30)
        • Linux (26)
        • Error (4)
      • 저작권과 스마트폰의 이해 (0)
      • 생각 뭉치 (6)
      • 궁금증 (2)
      • Private (4)
        • 이직 경험 (0)
        • 꿈을 찾아서 (1)
      • Android (21)
        • OS (4)
  • 블로그 메뉴

    • 홈
    • WEB
    • 알고리즘
    • DataBase
    • Linux
    • Mobile
    • C
    • 방명록
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    C++
    database
    algorithm
    JPA
    백준
    javascript
    linux
    android
    Kotlin
    PostgreSQL
    Programmers
    kafka
    Bitcoin
    Spring
    java
    docker
    ubuntu
    알고리즘
    Git
    프로그래머스
  • hELLO· Designed By정상우.v4.10.3
M_Falcon
[Spring] Autowired 사용시 주의 (Feat. Custom Annotation)
상단으로

티스토리툴바