스프링 입문-의존관계 주입

의존관계 주입

스프링 컨테이너를 사용한 의존관계의 주입에는 4가지 방법이 있다.

  • 생성자 주입
  • 필드 주입
  • 수정자 주입
  • 일반 메서드 주입
    이 있는데, 하나씩 형식과 장단점을 알아보자.

생성자 주입

private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy 
discountPolicy) {
 this.memberRepository = memberRepository;
 this.discountPolicy = discountPolicy;
}

가장 권장되고, 자주 사용되는 방식이다. 위와 같이 클래스의 생성자를 이용해서 클래스의 객체가 생성될 때, 즉 빈이 컨테이너에 올라갈 때 의존관계를 주입받는 방식이다.
가장 권장되는 만큼 생성자 주입에는 많은 장점들이 있는데, 첫째로 불변하다는 특징이 있다. final을 통해 상수 처리를 했기 때문에 객체가 생성될 때 한번만 주입되고 이후로는 변경되지 않는다. 실무에서는 거의 변경할 일이 없기때문에 불변하는 생성자 주입이 좋다고 강사님이 그러셨다.
두 번째로는 final의 특성 상 무조건 초기화가 되어야하기 때문에 의존관계 주입이 필요한 것을 알아채기 쉽다는 것이다. ide에서 에러로 표시헤주기 때문에 필수적으로 의존관계 주입을 해줘야하는 부분을 놓치고 지나가지 않게 해준다.
세 번째로는 빈의 생성과 의존관계 주입이 한번에 이루어진다는 것이다. 밑에 설명할 다른 방식들은 빈의 생성->의존관계 주입의 단계를 거쳐 컨테이너에 올라간다. 하지만 생성자 주입을 사용한다면 빈을 생성하며 의존관계 주입도 한번에 일어난다. 이건 장점이라기 보다는 생성자 주입의 특징에 더 가까운 것 같다.

수정자 주입

private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void setMemberRepository(MemberRepository memberRepository) {
    this.memberRepository = memberRepository;
}
@Autowired
public void setDiscountPolicy(DiscountPolicy discountPolicy) {
    this.discountPolicy = discountPolicy;
}

위와 같이 public인 setter로 외부에서 주입받는 것이 수정자 주입이다. 주로 의존관계의 주입에 추후 변경이 필요할 경우에 사용되는데, 위에서 설명했듯이 의존관계 주입을 변경할 일은 거의 일어나지 않으므로 수정자 주입 또한 거의 사용되지 않는다.
또한 public 접근지정자이므로 외부에서 누군가가 부주의하게 수정할 가능성이 있다는 것 또한 단점이다.

필드 주입

@Autowired private MemberRepository memberRepository;
@Autowired private DiscountPolicy discountPolicy;

위와 같이 필드에 바로 주입하는 방식이다. 간결하고 편해보이지만 스프링이 연결된 integrity 테스트에서만 동작하고 간단한 unit 테스트가 불가능하다.
또한 외부에서 간단하게 의존관계를 바꿔가며 테스트를 하는 것이 불가능하다. 권장되지 않는 방식이다.

일반 메서드 주입

private MemberRepository memberRepository;
private DiscountPolicy discountPolicy;
@Autowired
public void init(MemberRepository memberRepository, DiscountPolicy 
discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}

일반 메서드를 통해 주입받는 방식이다. setter 주입과 달리 한번에 많은 필드를 주입받을 수 있지만 그뿐이다. 장점도 단점도 setter 주입과 공유한다.

2개 이상의 빈 조회

@Autowired는 타입을 통해 빈을 조회한다. 따라서 같은 타입의 빈이 2개 이상일 때엔 문제가 발생한다. 이러한 문제를 해결하기 위한 방법들을 알아보자.

필드 명 매칭

@Autowired는 타입을 통해 매칭한 뒤, 빈이 여러 개 있다면 필드 이름 또는 파라미터 이름으로 한번 더 매칭을 시도한다.

  
@Autowired
private DiscountPolicy rateDiscountPolicy;

DiscountPolicy 타입의 빈이 fixDiscountPolicy, rateDiscountPolicy 두 클래스가 있을 때, 위와 같이 필드명을 클래스 이름으로 맞춰주면 해당 빈으로 의존관계를 주입시켜준다. 따라서 필드명, 파라미터명을 주의깊게 세팅시켜 주면 문제를 해결할 수 있다.

@Qualifier 사용

필드명 매칭은 간단하지만 이름이 중복되어 분별이 어려운 등의 문제가 새로 발생할 수 있다. @Qualifier 애너테이션을 사용하면 빈에 타입을 제외한 새로운 구별자를 붙여서 식별할 수 있다.

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

@Autowired
private @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy;

위와 같이 빈을 등록할 때 @Qualifier 애너테이션으로 새로운 식별자를 붙여주면, 이후 의존관계를 주입받을 때 타입 앞에 @Qualifier를 붙여서 해당 빈을 식별할 수 있다.

@Primary 사용

@Primary는 빈에 우선권을 부여하는 애너테이션이다.

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Autowired
private DiscountPolicy discountPolicy;

위와 같이 클래스에 붙여주면 해당 빈이 같은 타입중에 우선순위를 갖고 연결된다. @Qualifier의 경우 모든 코드에 애너테이션을 붙여줘야하는 만큼 상황에 따라 두 애너테이션을 적절히 사용하면 된다.
참고로 @Primary와 @Qualifier가 겹쳤을 경우에는 스프링은 자동보다 수동을 우선하므로 @Qualifier가 우선권을 갖는다.