[Spring] 의존관계 자동 주입2

안나·2024년 3월 13일
0

Spring

목록 보기
14/16
post-thumbnail

이 글은 인프런에서 김영한 님의 "스프링 핵심원리 - 기본편"을 수강 후 공부한 내용을 정리한 게시글입니다. 부족한 부분이 있다면 언제든 지적 부탁드립니다.


다양한 의존 관계 주입 방법에 대해서는 “이전 게시물”을 확인해주세요.
이전 게시물 - 의존관계 자동주입 1

✏️ 자동 주입 대상을 옵션으로 처리


주입할 스프링 빈이 없어도 동작해야 할 때가 있다.
그런데 @Autowired 만 사용하면 required 옵션의 기본값이 true 로 되어 있어서 자동 주입 대상이 없으면 오류가 발생한다. 이를 해결하기 위해서는 아래와 같이 3가지 방법을 가지고 옵션처리하여 해결할 수 있다.

아래 예시에서 Member 는 스프링 빈이 아니다.

  1. @Autowired(required=false) : 자동 주입할 대상이 없으면 수정자 메서드 자체가 호출 안됨

    //호출 안됨
    @Autowired(required = false) 
    public void setNoBean1(Member member) {
        System.out.println("setNoBean1 = " + member);
    }
    
    //결과 없음
  2. org.springframework.lang.@Nullable : 자동 주입할 대상이 없으면 null이 입력된다.

    //null 호출
    @Autowired
    public void setNoBean2(@Nullable Member member) {
        System.out.println("setNoBean2 = " + member);
    }
    
    //결과
    setNoBean2 = null
  3. Optional<> : 자동 주입할 대상이 없으면 Optional.empty 가 입력된다.

    //Optional.empty 호출
    @Autowired(required = false)
    public void setNoBean3(Optional<Member> member) 
        System.out.println("setNoBean3 = " + member);
    }
    
    //결과
    setNoBean3 = Optional.empty


✏️ 생성자 주입을 선택하자!


  • 생성자 주입 방식을 선택하는 이유는 여러가지가 있지만, 프레임워크에 의존하지 않고, 순수한 자바 언어의 특징을 잘 살리는 방법이기도 하다.
  • 기본으로 생성자 주입을 사용하고, 필수 값이 아닌 경우에는 수정자 주입 방식을 옵션으로 부여하면 된다. 생성자 주입과 수정자 주입을 동시에 사용할 수 있다.
  • 항상 생성자 주입을 선택해라! 그리고 가끔 옵션이 필요하면 수정자 주입을 선택해라. 필드 주입은 사용하지 않는 게 좋다.


✏️ 롬북 Lombok


생성자 주입을 작성하는 과정에서 비슷한 코드르 여러번 작성하게 된다.

다 형식이 정해져 있는 코드들이기때문에 이를 롬북이라는 라이브러리에서 코드 작성을 간소화 시켜준다.

  • @RequiredArgsConstructor 기능을 사용하면 final이 붙은 필드를 모아서 생성자를 자동으로 만들어준다. (다음 코드에는 보이지 않지만 실제 호출 가능하다.)


✏️ 특정 빈을 조회할 때 조회된 빈이 2개 이상이면 ?


ac.getBean(DiscountPolicy.class) 으로 DiscountPolicy스프링 빈 조회하려고 한다.

근데 이때 DiscountPolicy의 하위 타입인 FixDiscountPolicy , RateDiscountPolicy둘다 스프링 빈으로 선언되어 있어 ac.getBean(DiscountPolicy.class) 으로 조회하게 되면 반환되는 빈이 2개가 되어 NoUniqueBeanDefinitionException 오류가 발생한다.

NoUniqueBeanDefinitionException: No qualifying bean of type 
'hello.core.discount.DiscountPolicy' available: expected single matching bean 
but found 2: fixDiscountPolicy,rateDiscountPolicy

이때 하위 타입으로 지정할 수 도 있지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다. 그리고 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안된다.

@Autowired 필드명 매칭

@Autowired 는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.

@Autowired
private DiscountPolicy rateDiscountPolicy;
  • DiscountPolicy 타입으로 매칭을 시도하고 그 결과 여러 개의 빈이 존재할 경우 rateDiscountPolicy 라는 이름을 가진 빈이 있는지 추가로 확인한다.


@Qualifier @Qualifier끼리 매칭 빈 이름 매칭

@Qualifier 는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                        @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}
  • @Qualifier 가 mainDiscountPolicy 를 찾은 다음 매칭 한다.
  • 만약 @Qualifier 가 mainDiscountPolicy 인 빈이 없다면 빈 이름이 mainDiscountPolicy 빈을 찾아 매칭 시킨다.
  • 빈 이름도 동일한 빈이 없다면 NoSuchBeanDefinitionException 예외를 발생한다.

@Primary 사용

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

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}
//생성자
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                        DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
}
  • 우선순위가 더 높은 RateDiscountPolicy를 주입힌다.


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


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

즉, DiscountPolicy 타입의 모든 빈을 다 조회하고 싶은 경우를 말한다.

이런 경우에는 아래와 같이 List나 Map 자료구조로 모두 조회할 수 있다.

static class DiscountService {

    private final Map<String, DiscountPolicy> policyMap;
    private final List<DiscountPolicy> policies;
    
    public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
        this.policyMap = policyMap;
        this.policies = policies;
        
        System.out.println("policyMap = " + policyMap);
        System.out.println("policies = " + policies);
    }
}
  • Map<String, DiscountPolicy> : map의 키에 스프링 빈의 이름을 넣어주고, 그 값을 DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.
  • List : DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.

✏️ 빈 등록 결론


  • 편리한 자동 기능을 기본으로 사용하자
  • 직접 등록하는 기술 지원 객체는 수동 등록하자
  • 다형성을 적극 활용하는 비즈니스 로직은 수동 등록을 고민해보자

0개의 댓글