의존성 주입을 위해 필드 주입, setter주입, 생성자 주입 등 여러 가지 방법을 이용할 수 있다. 하지만, 프로그램에서 한 번 의존성을 셋팅하고 나면 바꿀 일이 없기 때문에 생성자 주입 방법이 권장된다.
의존성 주입
다음과 같이 NoteService에서 NoteRepository를 필요로 한다고 가정해보자. 이런 경우 생성자 또는 setter를 이용하여 noteRepository의 실제 객체를 주입 받아야 한다. 이런 것을 의존성 주입이라고 한다.
public class NoteService {
private NoteRepository noteRepository;
public NoteService(NoteRepository noteRepository) {
this.noteRepository = noteRepository;
}
}
스프링에서는 이런 의존성을 스프링 컨테이너가 관리해준다.
@Autowired
스프링에서는 의존성 주입을 위해 @Autowired라는 어노테이션이 이용된다. 이 어노테이션은 일반적으로 타입을 기준으로 주입을 받는다.
@Service
public class NoteService {
@Autowired
NoteRepository noteRepository;
}
타입을 기준으로 의존성을 주입받기 때문에 다음과 같이 인터페이스를 구현하고 있는 2개 이상의 구현 클래스가 있을 때 문제가 발생할 수 있다.
@Repository
public interface NoteRepository {}
@Repository
public class NoteA implements NoteRepository{}
@Repository
public class NoteB implements NoteRepository {}
@Service
public class NoteService {
@Autowired
NoteRepository noteRepository;
}
위의 코드를 작성하고 실행하면, 의존성 주입 과정에서 2개의 싱글톤 빈이 발견되어 주입을 할 수 없다고 나온다.
동일 타입 클래스 문제를 해결하는 방법
1. @Autowired
먼저 Autowired는 타입 매칭을 시도하고, 빈이 여러개가 있으면 필드 이름, 파라미터 이름으로 빈을 추가 매칭한다.
@Autowired
private DiscountPolicy discountPolicy
------- 수정 후 -------
@Autowired
private DiscountPolicy rateDiscountPolicy
이 경우 필드명이 rateDiscountPolicy이므로 rateDiscountPolicy가 정상 주입된다.
2. @Primary
우선순위를 정하는 방법으로 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
@Repository
@Primary
public class NoteA implements NoteRepository{}
3. @Qualifier
추가 구분자를 붙여주는 방법으로, 주입시 추가적인 정보를 제공하는 것이지 빈 이름을 변경하지는 않는다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy() {
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
만약 @Qualifier("mainDiscountPolicy")를 찾지 못하면, mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 만약 빈도 없으면 예외가 발생한다.
-------------
@Autowired를 사용한 의존성 주입의 경우, 빈 객체가 없으면 에러가 난다. 이 때 해결하는 방법을 옵션 처리에서 알아보자.