[스프링 핵심원리] - 6. 의존관계 자동 주입 (5)

Chooooo·2022년 11월 5일
0
post-thumbnail

이 글은 강의 : 김영한님의 - "스프링 핵심원리 - 기본편"을 듣고 정리한 내용입니다. 😁😁


어노테이션 직접 만들기

실무에서도 종종 애노테이션을 직접 만들어 사용한다.
저번 시간 @Qualifier 방식을 적용한 아래 코드를 보면, @Qualifier()안에 문자는 컴파일시 타입 체크가 안된다는 문제가 있다. 이러한 경우, 애노테이션을 만들어 다음과 같은 문제를 해결할 수 있다.

@MainDiscountPolicy 애노테이션 만들기

@Qualifier 애노테이션에 붙어있는 애노테이션 4개와 우리가 원래 RateDiscountPolicy에 붙였던 @Qualifier("mainDiscountPolicy") 애노테이션을 붙여서 새로운 @MainDiscountPolicy 애노테이션을 만들었다.

package hello.core.annotation;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
    // @MainDiscountPolicy 를 적용하면 위 애노테이션 기능이 모두 동작함
}

RateDiscountPolicy에 @MainDiscountPolicy 애노테이션 적용

기존에 주석처리된 부분 코드에서 @MainDiscountPolicy 를 적용하는 것으로 변경했다.

@Component
@MainDiscountPolicy
//@Qualifier("mainDiscountPolicy")    // 문자는 컴파일시에 오류 잡을 수X
public class RateDiscountPolicy implements DiscountPolicy{...}

OrderServiceImpl 의존 관계 주입 코드 수정

여기서는 생성자 주입방식을 사용하므로 OrderServiceImpl의 생성자 DiscountPolicy 파라미터 앞에 @Qualifier("mainDiscountPolicy") -> @MainDiscountPolicy 로 추가했다. 의존 관계 주입방식에 따라 아래와 같이 직접 만든 애노테이션을 붙여 사용할 수 있다.

//생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
	this.memberRepository = memberRepository;
	this.discountPolicy = discountPolicy;
}
//수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
	return discountPolicy;
}

애노테이션에는 상속이라는 개념이 없다. 이렇게 여러 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능이다. @Qulifier 뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용할 수 있다. 단적으로 @Autowired도 재정의 할 수 있다. 물론 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 더 혼란만 가중할 수 있다.

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

의도적으로 해당 타입의 스프링 모두가 필요한 경우도 있다.
예를 들어, 할인 서비스를 제공할 때 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있다고 가정해보자. 스프링을 사용하면 전략 패턴을 매우 간단하게 구현할 수 있다.
빈을 동적으로 선택해야할 때, Map으로 모든 빈을 조회해서 사용하면 다형성 코드를 유지하면서 빈을 동적으로 사용할 수 있다.

package hello.core.autowired.allbean;

import hello.core.AutoAppConfig;
import hello.core.discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
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;

import static org.assertj.core.api.Assertions.*;

public class AllBeanTest {

    @Test
    void findAllBean() {
        // AutoAppConfig, DiscountService 스프링 빈 등록
        // AutoAppConfig에서 컴포넌트 스캔을 통해 @Component가 붙은 것들 자동 빈 등록됨
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);

        DiscountService discountService = ac.getBean(DiscountService.class);
        Member member = new Member(1L, "userA", Grade.VIP);
        int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");

        // DiscountService 스프링 빈 잘 등록됐는지 검증
        assertThat(discountService).isInstanceOf(DiscountService.class);
        // 할인 금액이 1000원이 맞는지 검증
        assertThat(discountPrice).isEqualTo(1000);

        int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
        // 할인 금액이 2000원이 맞는지 검증
        assertThat(rateDiscountPrice).isEqualTo(2000);
    }

    static class DiscountService {
        private final Map<String, DiscountPolicy> policyMap;
        private final List<DiscountPolicy> policies;

        // 모든 DiscountPolicy를 주입
        @Autowired  // 생성자 1개이므로 생략O
        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) {
            // key(스프링 빈 이름) = discountCode인 할인 정책(DiscountPolicy) 조회
            DiscountPolicy discountPolicy = policyMap.get(discountCode);

            System.out.println("discountCode = " + discountCode);
            System.out.println("discountPolicy = " + discountPolicy);

            // 해당 (멤버, 금액)에 대해 계산한 할인 금액 리턴
            return discountPolicy.discount(member, price);
        }
    }
}

AutoAppConfig와 DiscountService가 스프링 빈으로 등록되고 AutoAppConfig 내의 컴포넌트 스캔으로 인해 @Component가 붙은 모든 클래스는 스프링 빈으로 자동 등록된다. rateDiscountPolicy, fixDiscountPolicy, discountService 모두 스프링 빈으로 등록되었으므로 @Autowired 로 생성자 주입을 해줄 수 있다.
1) DiscountService는 Map으로 모든 DiscountPolicy 를 주입받는다. 이때 DiscountPolicy의 하위타입인 fixDiscountPolicy, rateDiscountPolicy 가 주입된다.
2) discount() 메서드는 discountCode로 "fixDiscountPolicy"가 넘어오면 map에서 fixDiscountPolicy 스프링 빈을 찾아서 실행한다. “rateDiscountPolicy”가 넘어오면 rateDiscountPolicy 스프링 빈을 찾아서 실행한다.

주입 분석

1) Map : (key = 스프링 빈의 이름, value = DiscountPolicy 타입으로 조회한 스프링 빈)로 Map에 담는다.

2) List : DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다. (만약 해당하는 타입의 스프링 빈이 없으면, 빈 컬렉션이나 Map을 주입한다.)

profile
back-end, 지속 성장 가능한 개발자를 향하여

0개의 댓글