(인프런) 김영한님의 스프링 핵심 원리-기본편을 공부하고 리뷰한 글입니다.
7. 애노테이션 직접 만들기
실무에서도 종종 애노테이션을 직접 만들어 사용한다.
저번 시간 @Qualifier 방식을 적용한 아래 코드를 보면, @Qualifier()안에 문자는 컴파일시 타입 체크가 안된다는 문제가 있다. 이러한 경우, 애노테이션을 만들어 다음과 같은 문제를 해결할 수 있다.
@Component
@Qualifier("mainDiscountPolicy") // () 안에 문자는 컴파일시 타입 체크X
public class RateDiscountPolicy implements DiscountPolicy{...}
1. @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 를 적용하면 위 애노테이션 기능이 모두 동작함
}
2. RateDiscountPolicy에 @MainDiscountPolicy 애노테이션 적용
기존에 주석처리된 부분 코드에서 @MainDiscountPolicy 를 적용하는 것으로 변경했다.
@Component
@MainDiscountPolicy
//@Qualifier("mainDiscountPolicy") // 문자는 컴파일시에 오류 잡을 수X
public class RateDiscountPolicy implements DiscountPolicy{...}
3. 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도 재정의 할 수 있다. 물론 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 더 혼란만 가중할 수 있다.
8. 조회한 빈이 모두 필요할 때, List, Map
의도적으로 해당 타입의 스프링 모두가 필요한 경우도 있다.
예를 들어, 할인 서비스를 제공할 때 클라이언트가 할인의 종류(rate, fix)를 선택할 수 있다고 가정해보자. 스프링을 사용하면 전략 패턴을 매우 간단하게 구현할 수 있다.
빈을 동적으로 선택해야할 때, Map으로 모든 빈을 조회해서 사용하면 다형성 코드를 유지하면서 빈을 동적으로 사용할 수 있다.
1. AllBeanTest 코드 작성
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);
}
}
}
2. 로직 분석
AutoAppConfig와 DiscountService가 스프링 빈으로 등록되고 AutoAppConfig 내의 컴포넌트 스캔으로 인해 @Component가 붙은 모든 클래스는 스프링 빈으로 자동 등록된다. rateDiscountPolicy, fixDiscountPolicy, discountService 모두 스프링 빈으로 등록되었으므로 @Autowired 로 생성자 주입을 해줄 수 있다.
1) DiscountService는 Map으로 모든 DiscountPolicy 를 주입받는다. 이때 DiscountPolicy의 하위타입인 fixDiscountPolicy, rateDiscountPolicy 가 주입된다.
2) discount() 메서드는 discountCode로 "fixDiscountPolicy"가 넘어오면 map에서 fixDiscountPolicy 스프링 빈을 찾아서 실행한다. “rateDiscountPolicy”가 넘어오면 rateDiscountPolicy 스프링 빈을 찾아서 실행한다.
3. 주입 분석
1) Map : (key = 스프링 빈의 이름, value = DiscountPolicy 타입으로 조회한 스프링 빈)로 Map에 담는다.
2) List : DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다. (만약 해당하는 타입의 스프링 빈이 없으면, 빈 컬렉션이나 Map을 주입한다.)
Q. 생성자를 이용한 주입에서 어떻게 policyMap, policies에 자동으로 모든 빈이 주입이 되는가?
A. @Autowired가 붙은 메서드의 파라미터로 Map<String, BeanType>이 온다면 스프링은 BeanType에 해당하는 빈들을 모두 찾아서 Map으로 의존관계를 주입해준다. List<BeanType>이 오면 BeanType 에 해당하는 빈들을 모두 찾아 List로 의존관계를 주입해준다. 이는 스프링에서 제공하는 기능 중 하나다.
[참고] https://www.inflearn.com/questions/348704
3. 참고 - 스프링 컨테이너를 생성하면서 스프링 빈 등록하기
스프링 컨테이너를 생성하면서, 해당 컨테이너에 동시에 AutoAppConfig , DiscountService 를 스프링 빈으로 자동 등록한다.
new AnnotationConfigApplicationContext(AutoAppConfig.class,DiscountService.class);
1) new AnnotationConfigApplicationContext()를 통해 스프링 컨테이너 생성
2) AutoAppConfig.class , DiscountService.class 를 파라미터로 넘기면서 해당 클래스를 자동으로 스프링 빈으로 등록한다.
Q. AnnotationConfigApplicationContext()의 파라미터로 @Configuration이 붙은 클래스가 아닌 일반 클래스도 넣을 수 있는가?
A. 일반 클래스도 파라미터로 넣을 수 있다. 디폴트로 해당 클래스 자체는 스프링 빈으로 등록되고 @Configuration이 붙은 클래스의 경우, 해당 설정 정보에 따라 추가적으로 자동 빈 등록을 한다.
1) @Configuration이 붙은 클래스: 해당 클래스가 스프링 빈으로 등록됨, 해당 클래스 안의 @Bean, @ComponentScan 으로 @Component 클래스를 모두 자동 빈 등록
2) 일반 클래스: 해당 클래스가 스프링 빈으로 등록됨
'Spring > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
[스프링 핵심 원리] 08. 빈 생명주기 콜백 - 빈 생명주기 콜백 시작 (0) | 2022.05.20 |
---|---|
[스프링 핵심 원리] 07. 의존관계 자동 주입 - 자동, 수동의 올바른 실무 운영 기준 (0) | 2022.05.20 |
[스프링 핵심 원리] 07. 의존관계 자동 주입 - 조회 빈이 2개 이상(문제) & @Autowired 필드 명, @Qualifier, @Primary & 애노테이션 직접 만들기 (0) | 2022.05.19 |
[스프링 핵심 원리] 07. 의존관계 자동 주입 - 롬복과 최신 트랜드 (0) | 2022.05.18 |
[스프링 핵심 원리] 07. 의존관계 자동 주입 - 옵션 처리 & 생성자 주입을 선택해라! (0) | 2022.05.18 |