[Spring] 의존관계 자동 주입
본 포스트는 Inflearn
의 김영한님 강의를 바탕으로 작성했습니다.
의존관계 주입 방식
생성자 주입
@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;
}
}
- 생성자를 통해서 의존관계를 주입 받는 방식이다.
- 생성자 호출시점에 딱 1번만 호출된다. 변경이 불가능하지만 대부분의 경우 초기 세팅값을 변경할 일이 없다. (변경하면 실수할 가능성만 높아진다.)
- 불변, 필수 의존관계에 사용
- 생성자가 단 하나라면 @Autowired를 생략해도 된다.
수정자 주입
@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의 기본 동작은 의존관계를 주입할 대상이 없으면 오류가 발생한다.
- 주입 대상이 없어도 동작하게 하려면 @Autowired(required = false)로 설정하면 된다.
필드 주입 방식
@Component
public class OrderServiceImpl implements OrderService {
@Autowired
private MemberRepository memberRepository;
@Autowired
private DiscountPolicy discountPolicy;
}
- 외부에서 의존관계를 변경하지 못하기 때문에 테스트 하기가 힘들다는 치명적인 단점이 있다.
- DI 프레임워크가 없으면 아무것도 못한다.
- 왠만하면 사용하지 말고 설정 목적으로 하는 @Configuration 같은 곳에서만 필요할 때 사용하자.
일반 메서드 주입 방식
@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;
}
}
- 한번에 여러 필드를 주입받을 수 있는데 일반적으로 사용하지 않는다.
참고 : 의존관계 자동 주입은 스프링 컨테이너가 관리하는 스프링 빈이어야 작동한다. 빈이 아닌 클래스에 @Autowired 박는다고 적용되지 않는다.
결론 : 생성자 주입을 선택하고 필요할 때만 수정자 주입을 선별적으로 사용
- 이유
- 대부분의 의존관계 주입은 한번 일어나면 애플리케이션 종료까지 변경되지 않는다.
- 수정자 주입을 쓰려면 setter를 public로 열어놔야 하는데 실수가 발생할 가능성이 높아진다.
- 생성자 주입을 사용하면 final 키워드를 사용할 수 있어서 혹시 값이 설정되지 않으면 컴파일 시점에 에러를 던진다.
RequiredArgsConstructor
- lombok이 제공하는 기능으로 final이 붙은 필드를 모아서 생성자를 만들어준다.
조회 빈이 2개 이상일 때 발생하는 문제
- @Autowired는 타입으로 조회하기 때문에 같은 타입의 빈이 2개 이상일 때 문제가 발생한다. (NoUniqueBeanDefinitionException)
- 이를 해결하기 위한 방법을 몇 가지 소개하겠다.
@Autowired 필드 명 매칭
@Autowired
private DiscountPolicy rateDiscountPolicy;
- @Autowired는 우선 타입으로 매칭을 시도하고 만약 빈이 2개 이상 찾아지면 필드 명, 파라미터 명으로 빈 이름을 매칭시킨다.
- 위에서는 rateDiscountPolicy라는 빈 이름으로 시도한다.
@Qualifier
- 빈 등록시에 @Qualifier를 붙여준다. (아래 코드 참고)
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
- 주입하는 부분에서 @Qualifier를 붙여주고 등록한 이름을 적어준다. (아래 코드 참고)
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
- @Qualifier는 우선 @Qualifier끼리 매칭하고 대상이 없으면 빈 이름으로 매칭한다. 만약 빈 이름도 없으면 NoSuchBeanDefinitionException을 발생시킨다.
@Primary
- @Primary는 작동방식이 단순한다. 여러 빈이 매칭되면 @Primary가 붙은 것이 우선권을 가진다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
- DiscountPolicy를 주입할 때, RateDiscountPolicy, FixDiscountPolicy가 매칭되는데 @Primary가 붙은 RateDiscountPolicy가 선택된다.
우선순위
- @Qualifier는 구체적으로 작동하고 @Primary는 기본값처럼 작동한다. 더 구체적인 것이 우선권을 가지는게 일반적이다.
- @Qualifier와 @Primary가 겹치면 @Qualifier가 우선권을 가진다.
에노테이션 만들기
- @Qualifier(“~~~”) 처럼 문자로 넘기면 컴파일 시점에 타입체크가 불가능하다.
- 에노테이션을 만들어서 사용하면 컴파일 시점에 타입체크를 할 수 있다. (코드 참고)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
- 위와 같이 코드를 작성하면 @MainDiscountPolicy라는 에노테이션이 만들어진다.
- @MainDiscountPolicy를 사용하면 이제 컴파일 시점에 타입체크를 할 수 있다.
실무에서는 어떻게 해야하는가?
- 자동을 기본으로 사용하자!
- 직접 기술 지원 객체를 스프링 빈으로 등록한다면 자동이 아니라 수동으로 등록해서 명확하게 하자!
- 다형성을 적극적으로 활용하는 로직은 수동 등록을 고민해보자!
출처
- https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8