앞의 글에서 말했듯이 스프링에서는 컴포넌트 스캔을 통해 자동으로 스프링 빈을 등록하고, 의존관계 설정을 해 줄 수 있다.
좀 더 구체적으로 말하자면 @Component를 클래스에 적용하여, 해당 클래스를 스프링 빈으로 등록할 수 있고, @Autowired를 통해 의존관계 주입을 해 줄 수 있다.
지난 글에서는 @Component를 활용한 자동 스프링 빈 등록에 대해 알아보았고, 이번 글에서는 @Autowired를 통해 의존관계를 주입하는 방법을 더 자세히 알아보고자 한다.
다양한 의존관계 주입 방법
@Autowired를 활용하여 의존관계를 주입하는 방법은 생성자 주입, 수정자 주입, 필드 주입, 일반 메서드 주입이 있다.
생성자 주입
생성자 주입은 말 그대로 생성자를 통해 의존관계를 주입하는 방법이다.
생성자 호출 시점에 딱 1번만 주입되는 것이 보장되기 때문에, 불변, 필수 의존관계에 사용한다.
생성자에 @Autowired를 적용하여 생성자 주입을 구현할 수 있으며, 만약 생성자가 1개만 있을 경우 @Autowired를 생략해도 의존관계 주입이 된다.
아래의 코드에서도 생성자가 1개이므로, @Autowired를 생략해도 의존관계 주입이 된다.
@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;
}
}
수정자 주입(setter)
수정자 주입은 setter를 통해 의존관계를 주입하는 방법이다.
수정자 주입의 경우 생성자 주입과는 다르게 스프링 빈 객체 생성 후, setter를 직접 호출하여 구현체를 변경할 수 있다.
따라서 선택, 변경 가능성이 있는 의존관계에 사용한다.
@Component
public class OrderServiceImpl implements OrderService {
private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
}
필드 주입
필드에 @Autowired를 적용하여 의존관계를 주입하는 방법이다.
코드가 제일 간결한 의존관계 주입 방법이지만, 외부에서 변경이 불가능하여 테스트하기 힘들다는 큰 단점이 있다.
공식적으로도 권장하지 않는 의존관계 주입 방법이기 때문에 그냥 간단하게 사용하지 않는 것이 좋다.
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
일반 메서드 주입
별도의 메서드를 생성하고, 해당 메서드에 @Autowired를 적용하여 의존관계를 주입하는 방법이다.
일반적으로 잘 사용하지 않는 방법이다.
@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;
}
}
생성자 주입 권장
스프링을 포함한 대부분의 DI 프레임워크들이 생성자 주입을 권장하고 있다.
불변
우선 의존관계는 어플리케이션이 구동된 이후로 변경될 일이 극히 드물다.
그리고 수정자 주입 또는 일반 메서드 주입을 사용할 경우, 누군가가 의존관계를 변경할 수 있는 위험성이 있다.
어플리케이션이 구동될 때 의존관계를 설정하고, 이후로는 불변을 보장하는 생성자 주입을 사용하는 것이 바람직하다.
final
또한 생성자 주입만이 의존관계 주입이 필요한 필드에 final을 사용할 수 있게 해준다.
final 키워드를 필드에 적용할 경우 생성자를 통해 반드시 값을 설정해줘야 하기 때문에, 만약 주입될 값이 설정되지 않더라도 컴파일 과정에서 오류를 빠르게 확인하고 수정할 수 있다.
대표적으로 이러한 이유들 때문에 생성자 주입을 권장한다. 의존관계 주입에 대한 권장 가이드 라인은 아래와 같다.
- 기본적으로는 생성자 주입을 사용한다.
- 만약 필수 값이 아닌 경우에는, 수정자 주입 방법을 통해 의존관계를 주입한다.
동일 타입의 빈이 2개 이상일 때 의존관계 자동 주입
@Autowired는 기본적으로 타입을 통해 빈 객체를 조회한다.
DiscountPolicy 인터페이스의 구현체 FixDiscountPolicy, RateDiscountPolicy가 빈으로 등록되어있다고 가정하자.
DiscountPolicy 타입의 필드에 @Autowired를 적용하게 되면, NoUniqueBeanDefinitionException이 발생한다.
NoUniqueBeanDefinitionException는 DiscountPolicy 타입의 빈이 2개 이상이기 때문에 어떤 빈을 주입해줘야 할 지 모르겠다는 의미이다.
이러한 문제를 해결하기 위해 @Autowired 필드명 매칭, @Qualifier, @Primary를 활용할 수 있다.
@Autowired 필드명 매칭
@Autowired 필드명 매칭 방법은 우선 @Autowired의 기존 방식 대로 타입 매칭을 시도하고 조회된 빈이 2개 이상일 경우, 필드명 또는 파라미터명을 통해 추가 매칭을 시도하는 방법이다.
아래의 코드처럼 필드명을 rateDiscountPolicy로 설정할 경우, 타입 매칭 실패 후 필드명 매칭을 통해 rateDiscountPolicy 빈이 주입되게 된다.
@Autowired
private DiscountPolicy rateDiscountPolicy
@Qualifier
빈으로 등록할 클래스에 @Qualifier("식별자")를 적용해주는 방법이다.
아래의 코드처럼 RateDiscountPolicy, FixDiscountPolicy에 @Qualifier을 적용하고, 의존관계 주입 코드에도 @Qualifier을 적용하여 주입될 빈을 지정해준다.
아래의 코드에서는 rateDiscountPolicy 구현체가 주입될 것이다.
// <OrderServiceImpl.java>
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
// <RateDiscountPolicy.java>
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
// <FixDiscountPolicy.java>
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
@Primary
스프링 빈을 등록할 때 @Primary를 적용해 줄 수 있다.
의존관계 주입 시 동일 타입의 빈이 2개 이상인 경우, @Priamry가 적용된 빈이 우선적으로 주입된다.
아래의 코드를 보면 RateDiscountPolicy에 @Primary가 적용되어 있기 때문에 의존관계 주입시 rateDiscountPolicy 빈이 주입된다.
// <RateDiscountPolicy.java>
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
// <FixDiscountPolicy.java>
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
// <OrderServiceImpl.java>
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
{
일반적으로 @Qualifier 또는 @Primary가 주로 사용된다.
@Qualifier가 @Primary보다 더 우선권을 가진다는 것을 주의해야한다.