2 분 소요

본 포스트는 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

태그:

카테고리:

업데이트: