동적으로 빈을 선택해야할 때, Map으로 빈을 받으면 편리하다.
예제에서는 두가지 할인 정책이 있는데, Map에 key값만 넘겨주면 각 할인 정책에 맞는 할인가를 알 수 있다.
1. 예제: Map으로 2개의 빈 받아오기
DiscountPolicy 인터페이스가 있다.
fixDiscountPolicy와 rateDiscountPolicy는 구현 클래스다.
✅FixDiscountPolicy
@Component
public class FixDiscountPolicy implements DiscountPolicy {
private int discountFixAmount = 1000; //1000원 할인
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP){
return discountFixAmount;
} else {
return 0;
}
}
}
@Component를 붙여줘서 빈에 등록시킨다.
항상 1000원을 할인하는 코드다.
✅RateDiscountPolicy
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy{
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP){
return price * discountPercent / 100;
} else {
return 0;
}
}
}
직접 만든 어노테이션인 @MainDiscountPolicy를 붙여서 의존관계 생성자 자동주입시 RateDiscountPolicy를 선택하도록 한다. (빈 충돌 방지)
🤔의문점
- Map 또는 List에 두가지 정책을 담을 때 어떻게 @MainDiscountPolicy가 붙은 RateDiscountPolicy만 들어오는게 아니라 FixDiscountPolicy까지 들어오지?
💡의문 해결
- @MainDiscountPolicy을 생성자에 사용함으로써 생성자로 초기화하여 값을 넣을 때, 우선적으로 의존성 주입을 받는 것이다. @Qualifier을 써도 @Component가 붙은 DiscountPolicy의 하위클래스들이 Bean으로 등록된다.
*참고
@MainDiscountPolicy는 @Qualifier기능을 담고 있으며 컴파일시 타입을 확인할 수 있게하는 어노테이션이다.
✅ 2개의 빈을 Map 또는 List에 넣은 테스트
public class AllBeanTest {
// 테스트 클래스 선언
@Test
// 테스트 메서드 선언
void findAllBean(){
// Spring 컨텍스트를 AutoAppConfig.class, DiscountService.class를 이용해 생성
// AutoAppConfig.class 안넣으면 DiscountService에 있는 Map, List객체 빈값들어옴.
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
// 컨텍스트에서 DiscountService 타입의 빈을 가져옴
DiscountService discountService = ac.getBean(DiscountService.class);
// Member 인스턴스 생성
Member member = new Member(1L, "userA", Grade.VIP);
// DiscountService를 이용해 fixDiscountPolicy정책 할인 가격 계산
int discountPrice = discountService.discount(member,10000,"fixDiscountPolicy");
// 계산된 할인 가격이 1000원인지 확인
Assertions.assertThat(discountPrice).isEqualTo(1000);
// 다른 할인 정책을 사용하여 rateDiscountPolicy정책 할인 가격 계산
int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
// 계산된 할인 가격이 2000원인지 확인
Assertions.assertThat(rateDiscountPrice).isEqualTo(2000);
}
// 테스트용 내부 정적 클래스 따로 만들음.
@Component
static class DiscountService{
// 할인 정책을 저장하는 Map
private final Map<String, DiscountPolicy> policyMap;
// 할인 정책 객체 리스트
private final List<DiscountPolicy> policies;
// 생성자에 @Autowired를 사용하여 의존성 주입
@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
// policyMap과 policies 내용을 콘솔에 출력
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
// 할인 코드에 따라 할인 가격 계산 메서드
public int discount(Member member, int price, String discountCode) {
// 할인 정책을 Map에서 가져옴
DiscountPolicy discountPolicy = policyMap.get(discountCode);
// 할인 정책에 따라 할인 가격 계산 후 반환
return discountPolicy.discount(member, price);
}
}
}
1. Map의 value 타입과 List 의 타입이 DiscountPolicy다.
2. DiscountPolicy 타입으로 된 Bean을 탐색한다.
3. AutoAppConfig에서 ComponentScan을 통해서 이미 스프링 빈으로 등록해둔 FixDiscountpolicy, RateDiscountPolicy를 get메서드를 이용해서 가져온다.
-> Test코드에서 getBean으로 내부 정적클래스인 DiscountService 빈을 가져온다.
4. 어떤 정책을 사용할 지 String으로 지정해서 사용할 수 있다.
5. 이렇게 Map 또는 List로 하위타입 빈을 담으면 다형성을 적극 활용할 수 있는 코드가 된다.
✅Map과 List에 담기는 값 확인
policyMap = {fixDiscountPolicy=hello.core.discount.FixDiscountPolicy@30e92cb9, rateDiscountPolicy=hello.core.discount.RateDiscountPolicy@7fae4d4a}
policies = [hello.core.discount.FixDiscountPolicy@30e92cb9, hello.core.discount.RateDiscountPolicy@7fae4d4a]
policyMap
Key - String타입의 할인정책 클래스
Value - DiscountPolicy타입의 빈 인스턴스
아래와 같이 Key값으로 value를 조회한다.
// 할인 정책을 Map에서 가져옴
DiscountPolicy discountPolicy = policyMap.get(discountCode);
FixDiscountPolicy 또는 RateDiscountPolicy의 discount메서드를 호출할 수 있다.
discountPolicy.discount(member, price);
2. OCP와 전략패턴
이러한 방식은 객체지향 설계 원칙 중 개방-폐쇄 원칙 (Open-Closed Principle, OCP) 을 따른다.
Map에서 알고리즘을 꺼내 사용하는 방식으로 전략패턴을 구현할 수 있다.
1️⃣확장에 열려 있음
- 새로운 전략(알고리즘)이 필요할 경우, 새로운 전략 클래스를 만들고 스프링 빈으로 등록하기만 하면 된다. 이 클래스를 Map에 추가하기만 하면, 시스템의 나머지 부분을 변경할 필요 없이 새로운 기능을 사용할 수 있다.
2️⃣수정에 닫혀 있음
- 기존의 코드(예를 들어 DiscountService와 같은 서비스)는 전략이 추가되거나 변경될 때 수정할 필요가 없다.
Map에서 필요한 전략을 선택하기만 하면 되기 때문에, 기존 코드는 변경하지 않아도 된다.
3. 의존성 수동 주입과 자동 주입은 언제 써야하는가
✔️비즈니스 로직은 웬만하면 자동 주입을 하는게 좋다.
- 실무에서는 관리하는 빈이 많아질수록 유지보수하기가 어려워지기 때문이다.
✔️기술지원 로직
- 주요 비즈니스 로직이 아닌 비즈니스 로직이 돌아가게끔 하기위한 데이터베이스 연결같은 기술지원로직은 수동주입을 하는게 좋다.
✔️비즈니스 로직이어도 다형성을 적극 활용하는 경우
- 빈을 직관적으로 파악하기 위해 수동 주입을 하는게 좋다.
'인프런 김영한 강의 정리 > 스프링 핵심원리 기본편' 카테고리의 다른 글
스프링 컨텍스트 이해와 선택: 애플리케이션 요구에 맞는 올바른 컨텍스트 사용하기 (0) | 2024.05.07 |
---|---|
스프링 핵심 원리 기본편 - 빈 생명주기 콜백 (0) | 2024.05.07 |
스프링 핵심 원리 기본편 - 의존관계 자동 주입(4)|조회하는 빈이 2개 이상,해결법, 애노테이션 직접 만들기 (0) | 2024.05.06 |
스프링 핵심원리 기본편 - 의존관계 자동 주입(3)|롬복과 최신 트랜드 (0) | 2024.05.06 |
스프링 핵심 원리 기본편 - 의존관계 자동 주입(2)|옵션처리 (0) | 2024.05.04 |