앞에서 간단하게 DI에 대해서 알아봤다.
[Spring Framework] IOC와 DI
IOC와 DI에 대해서 알아보기 전에 순수 자바코드를 먼저보자. public class MemberServiceImpl implements MemberService{ private final MemberRepository memberRepository = new MemorymemberRepository(); @Override public void join(Member membe
hyunbenny.tistory.com
여기서는 의존관계 주입 방법에 대해서 알아보자.
1. 생성자 주입 방법
생성자를 통해서 의존관계를 주입받는 방법
- 생성자가 호출되는 시점에 단 1번만 호출되는 것이 보장되는 방법이다.
- 변하지 않거나 필수적인 의존관계에서 사용한다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
- 생성자가 딱 하나만 있는 경우에는 @Autowired를 생략해도 스프링 컨테이너가 알아서 자동으로 주입해준다.
2. 수정자 주입 방법
수정자 메서드(setter)를 통해서 의존관계를 주입받는 방법
- 변경 가능성이 있거나, 선택을 해야 하는 의존관계에서 사용한다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository){
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy){
this.discountPolicy = discountPolicy;
}
...
}
- 스프링 컨테이너가 OrderServiceImpl을 스프링 컨테이너에 빈으로 등록 하면서 @Autowired가 있는 애들을 의존관계 주입한다.
- 생성자 주입방식은 스프링 빈을 등록하면서 의존관계 주입이 같이 발생한다.(생성자를 호출할 때 어쩔 수 없이 의존관계 주입이 필요함)
3. 필드 주입 방법
필드에 바로 주입하는 방법
- 코드가 간결해지고 외부에서 변경이 불가능하다.
- 그렇기 때문에 수정을 하기 위해서 결국에는 setter가 필요하다.
- ➡️ 그럼 그냥 수정자 주입 방법을 쓰지...?
- DI 프레임워크가 없으면 아무것도 할 수 없다.
- 쓰지마!!!
@Component
public class OrderServiceImpl implements OrderService{
@Autowired
private final MemberRepository memberRepository;
@Autowired
private final DiscountPolicy discountPolicy;
...
}
4. 일반 메서드 주입 방법
일반적인 메서드를 통해서 의존관계를 주입받는 방법
- 한번에 여러 개의 필드를 주입 받을 수 있다.
- 잘 안쓴다..
@Component
public class OrderServiceImpl implements OrderService{
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy){
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
}
그렇다면 무슨 방법을 쓰는 것이 좋을까?
➡️ 생성자 주입 방법
왜???
1) 보통의 애플리케이션은 의존관계를 한번 주입하고 나면 종료될 때까지 변경할 일이 거의 없다.
- 수정자 주입 방법을 사용하여 setter를 public으로 모두가 다 사용할 수 있게 해두면, 누군가의 실수 등으로 변경될 수 있다.
- 그리고 변경되면 안되는 메서드인데 굳이 public으로 열어두는 게 좋지 않다.
- 그렇기 때문에 생성자 주입 방법을 사용하여 객체 생성 시 최초 1번만 호출되고 더 이상 호출될 일이 없도록 함으로써 불변한 설계를 할 수 있다.
2) 생성자 주입 방법을 사용하면 주입해야 할 의존관계를 빠뜨릴 경우, 컴파일 에러가 발생한다.
- 생성자 주입을 사용하면 final키워드를 사용할 수 있는데 이는 값이 설정되지 않는 오류를 컴파일 시점에 막아준다.
- 생성자 주입 방법을 제외한 다른 주입 방법들은 생성자 호출 이후에 호출되므로 final키워드를 사용할 수 없다.
조회할 빈이 2개 이상인 경우
의존관계 자동주입을 하고 조회할 빈의 타입이 2개가 있으면 NoUniqueBeanDefinitionException 발생한다.
[Spring Framework] 스프링 컨테이너와 스프링 빈
1. 스프링 컨테이너란 주로 ApplicationContext 인터페이스를 스프링 컨테이너라고 한다. * 더 정확히는 BeanFactory, ApplicationContext로 구분해야 한다. 그럼 BeanFactory와 ApplicationContext는 또 뭐냐...? 🔸 BeanFa
hyunbenny.tistory.com
이 때, 하위타입을 지정해서 해결하는 방법이 있지만 DIP를 위반하고 유연성이 떨어진다. 또한 이름만 다르고 완전히 똑같은 타입이 2개 있으면 해결이 안된다.
해결방법은 없을까?
1) @Autowired 필드명으로 매칭
type으로 매칭을 시도하고 빈이 여러개 있으면 필드 이름, 파라미터 이름으로 빈 이름을 매칭
// ------------ 기존 코드 ----------------
@Autowired
private DiscountPolicy discountPolicy
// ------------ @Autowired 필드명 매칭 ----------------
@Autowired
private DiscountPolicy rateDiscountPolicy
2) @Qualifier
빈 이름을 변경하는 것이 아니라 빈을 구분할 수 있는 추가적인 방법을 제공한다.
(@Qualifier끼리 매칭 → 빈 이름 매칭 → 못 찾으면 NoSuchBeanDefinitionException발생)
@Component
@Qualifier(value = "mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{ ... }
@Component
@Qualifier(value = "fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy{ ... }
// 생성자 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy){
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
// 수정자 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
return discountPolicy;
}
- RateDiscountPolicy, FixDiscountPolicy가 모두 DiscountPolicy인터페이스를 구현하고, @Component를 통해 빈으로 등록하였다.(@Qualifier를 사용해서 다른 )
- OrderServiceImpl클래스에서 DiscountPolicy인터페이스를 주입받으려고 하는데 같은 타입의 빈이 2개가 있어 NoUniqueBeanDefinitionException이 발생할 수 있다.
- 왜 RateDiscountPolicy나 FixDiscountPolicy 타입으로 주입받지 않는지는 아래를 한 번 보자.
[Spring Framework] IOC와 DI
IOC와 DI에 대해서 알아보기 전에 순수 자바코드를 먼저보자. public class MemberServiceImpl implements MemberService{ private final MemberRepository memberRepository = new MemorymemberRepository(); @Override public void join(Member membe
hyunbenny.tistory.com
- 이 때, @Qualifier를 이용해서 내가 주입받으려고 하는 빈이 어떤 빈인지 확실하게 지정을 해줄 수 있다.
- 그러면 의존관계 주입을 할 때 @Qualifier가 붙어 애들 중에서 같은 이름을 가진 빈을 찾아 주입을 해준다.
@Qualifier("mainDiscountPolicy")에서 "mainDiscountPolicy" 이 문자는 컴파일 시 타입 체크가 안된다는 문제점이 있다.
이게 무슨 말이냐..?
문자열이기 때문에 오타가 발생하여 mainDisCountPolicy 라고 적어도 컴파일 시점에 오류를 발생할 수 없다는 것이다.
➡️ 빈을 찾지 못해 NoSuchBeanDefinitionException이 발생할 것이다.
어노테이션을 만들어 사용함으로써 이러한 문제점을 해결할 수 있겠다.
3) @Primary
빈들 중 우선순위를 정해준다.(@Primary가 붙어있는 빈이 우선권을 가진다.)
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
//생성자 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
- RateDiscountPolicy, FixDiscountPolicy가 모두 등록될 경우 DiscountPolicy타입으로 의존관계를 주입받게 되면 등록된 빈이 2개라 NoUniqueBeanDefinitionException이 발생하는데
- 이 때, @Primary을 사용해서 특정한 빈에게 우선권을 주면
- 의존관계를 주입 받을 때, @Primary가 붙은 RateDiscountPolicy가 주입된다.
- @Primary를 사용하면 @Qualifier를 붙일 필요가 없다.
@Primary와 @Qualifier의 우선 순위
스프링은 자동보다 수동, 넓은 범위보다는 좁은 범위를 가진 것이 우선권을 가진다.
따라서 @Primary와 @Qualifier가 같이 있는 경우에는 @Qualifier가 우선순위가 높다.
근데 진짜 빈이 2개 다 필요하면 어떻게 하지?
➡️ List나 Map을 사용해서 모두 다 주입받는다.
public class AllBeanTest {
@Test
void findAllBean() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member user = new Member(1L, "user", Grade.VIP);
int discountPrice = discountService.discount(user, 10000, "fixDiscountPolicy");
assertThat(discountService).isInstanceOf(DiscountService.class);
assertThat(discountPrice).isEqualTo(1000);
int rateDiscountPrice = discountService.discount(user, 20000, "rateDiscountPolicy");
assertThat(rateDiscountPrice).isEqualTo(2000);
}
static class DiscountService{
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
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 user, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(user, price);
}
}
}
- 사용하는 입장에서 파라미터로 DiscountPolicy를 사용할 지 넘겨주면(discountService.discount(user, 20000, "rateDiscountPolicy"))
- DiscountService에서는 모든 DiscountPolicy타입을 가지고 있는 map이나 list에서 "rateDiscountPolicy" 빈을 찾아 해당 빈의 discount()를 동작시킨다.
'Spring > Spring Framework' 카테고리의 다른 글
[Spring Framework] 서블릿(Servlet) (0) | 2023.06.07 |
---|---|
[Spring Framework] 웹 애플리케이션의 이해 (0) | 2023.05.30 |
[Spring Framework] @ComponentScan (0) | 2023.04.10 |
[Spring Framework] 스프링 컨테이너와 싱글톤 컨테이너 (0) | 2023.04.10 |
[Spring Framework] 스프링 컨테이너와 스프링 빈 (0) | 2023.04.06 |