코딩하는 감쟈

스프링 핵심 개념원리6 본문

Back-End/spring

스프링 핵심 개념원리6

진감쟈 2025. 7. 12. 15:16

다양한 의존관계 주입 방법

  • 생성자 주입

  • 수정자 주입

  • 필드 주입

  • 일반 메서드 주입

    1. 생성자 주입

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
    }
  • 불변, 필수 의존관계에 사용

  • 생성자가 한개뿐이라면 생략 가능

    2. 수정자 주입

    @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;
      }
    }
  • 선택, 변경 가능성이 있는 의존관계에서 사용

    3. 필드 주입

    @Component
    public class OrderServiceImpl implements OrderService {
    
      @Autowired
      private MemberRepository memberRepository;
    
      @Autowired
      private DiscountPolicy discountPolicy;
    }
  • 외부에서 변경이 불가능하다는 단점

  • DI 프레임워크가 없으면 아무것도 할 수 없다.

  • 사용하지 말자

    • 실제 코드와 상관없는 테스트 코드에서는 사용해도 괜찮다.

      4. 일반 메서드 주입

      @Autowired
      public void init(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
      }
  • 거의 사용 안함

    옵션 처리

    @Autowired(required = false)
    public void setNoBean1(Member noBean1) {
      System.out.println("noBean1 = " + noBean1);
    }
    

@Autowired(required = false)
public void setNoBean2(@Nullable Member noBean2) {
System.out.println("noBean1 = " + noBean2);
}

@Autowired(required = false)
public void setNoBean3(Optional noBean3) {
System.out.println("noBean1 = " + noBean3);
}

### 생성자 주입을 선택해라!
- 대부분의 의존관계 주입은 한번 일어나면 끝날 때까지 의존관계를 변경할 일이 없다.
- 변경하면 안되는 메서드를 열어두는 것은 좋은 설계 방법이 아니다.
```java
@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;
    }
}
OrderServiceImpl orderService = new OrderServiceImpl(memberRepository, new FixDiscountPolicy()); // 컴파일 오류
  • 수정자 주입과는 다르게 생성자 주입을 사용해서 테스트를 할때, 컴파일 오류가 발생한다. 따라서 IDE에서 어떤 값을 필수로 주입해야 하는지 알 수 있다.
  • final 키워드
    • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. 그래서 생성자에서 혹시라도 값이 설정되지 않는 오류를 컴파일 시점에서 막아준다.

      롬복과 최신 트랜드

  • 이거 꼭 활성화해야함
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;
}
  • @RequiredArgsConstructor를 사용하면, final이 붙은 필드를 모아 아래 생성자를 자동으로 만들어준다.
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
    }

    조회 빈이 2개 이상 문제

  • DiscountPolicy에 빈이 두개 등록되어있어서 문제
    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
    }
  • 해결방법
    • @Autowired 필드명 매칭
    • @Qualifier -> @Qualifier끼리 매칭 -> 빈 이름 매칭
    • @Primary 사용

      1. @Autowired 필드명 매칭

      @Autowired는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
      @Autowired
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = rateDiscountPolicy;
      }
  • 이렇게 rateDiscountPolicy처럼 구체적인 필드 이름을 넣어주면 해결된다.

    2. @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라는 이름의 스프링 빈 이름을 찾는다.

    3. @Primary 사용

  • 우선순위를 정하는 방법이다. @Autowired시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.

    @Component
    @Primary
    public class RateDiscountPolicy implements DiscountPolicy {}

    조회한 빈이 모두 필요할 때, List, Map

  • 의도적으로 해당 타입의 스프링 빈이 다 필요한 경우도 있다.

    static class DiscountService {
      private final Map<String, DiscountPolicy> policyMap;
      private final List<DiscountPolicy> policys;
    
      @Autowired
      public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policys) {
          this.policyMap = policyMap;
          this.policys = policys;
      }
    
      public int discount(Member userA, int price, String discountCode) {
          DiscountPolicy discountPolicy = policyMap.get(discountCode);
          return discountPolicy.discount(userA, price);
      }
    }

'Back-End > spring' 카테고리의 다른 글

스프링 핵심 개념원리8  (0) 2025.07.12
스프링 핵심 개념원리7  (1) 2025.07.12
스프링 핵심 개념원리5  (0) 2025.07.12
스프링 핵심 개념원리4  (1) 2025.07.12
스프링 핵심 개념원리3  (1) 2025.07.12