본문 바로가기
개발/Spring

[스프링] 스프링 핵심원리 - 의존관계 자동주입

by 윤호 2021. 12. 6.

의존관계 주입 방법 4 가지

  • 생성자 주입
  • 수정자 주입 (setter 주입)
  • 필드 주입
  • 일반 메서드 주입

기본적으로 생성자 주입을 사용하고, 가끔 옵션이 필요하면 수정자 주입을 선택한다.

생성자 주입

  • 생성자에 @Autowired를 붙여 사용
  • 생성자가 딱 1개 있을 경우 @Autowired를 생략해도 됨
  • 생성자 호출 시점에 딱 1번만 호출되는 것이 보장된다.
  • 불변, 필수 의존관계에 사용 (필수는 null 값이 들어가면 안되는 걸 의미)
  • 생성자 주입을 사용하면 필드에 final 키워드를 사용할 수 있다. -> 값이 설정되지 않으면 오류를 컴파일 시점에서 막아줌
  • 롬복 @RequiredArgsConstructor를 사용하여 간단하게 생성자 주입을 설정할 수 있음

수정자 주입 (setter 주입)

  • setter에 @Autowired를 붙여 사용
  • 선택, 변경 의존관계에 사용
  • 주입할 대상이 없어도 동작하도록 하려면 @Autowired(required = false)로 사용

필드 주입 (권장하지 않음)

  • 필드에 @Autowired를 붙여 사용
  • 코드를 간결하게 할 수 있지만
  • 외부에서 변경할 수 없어 테스트하기에 어려움이 생긴다 (순수 자바코드로 테스트 할 수 없음)
  • DI 프레임워크가 없으면 테스트할 수가 없다. - 다른 방법은 프레임 워크가 없어도 생성자나 setter로 주입할 수 있음
  • 사용해도 되는 곳 : 애플리케이션과 관련없는 테스트 코드, 스프링 설정을 목적으로하는 @Configuration

일반 메서드 주입

  • 일반 메서드에 @Autowired를 붙여 사용
  • 한번에 여러 필드를 주입 받을 수 있다
  • 일반적으로 잘 사용하지 않는다.

 

옵션처리

@Autowired(required = false)

  • 주입할 빈이 없으면 아예 호출되지 않음

@Nullable

  • 주입할 빈이 없으면 Null값이 들어감

Optional<>

  • 주입할 빈이 없으면 Null과 비슷하게 empty로 설정됨
@Autowired(required = false)
public void setNoBean1(Member noBean1) {
    System.out.println("noBean1 = " + noBean1);
}

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

@Autowired
public void setNoBean3(Optional<Member> noBean3) {
    System.out.println("noBean3 = " + noBean3);
}

 

조회 빈이 2 개 이상일 경우

@Autowired는 컨테이너에 동일한 타입의 빈을 꺼내서 주입한다. 동일한 타입의 빈이 2 개 이상이면 충돌이난다.

NoUniqueBeandefinitionException 오류

 

이 경우 해결 방법은 3 가지가 있다.

  • @Autowired 필드명 매칭
    • autowired는 타입이 같은걸 조회하고, 결과가 2 개 이상이면 파리미터명/필드명과 같은 것을 매칭함
  • @Qualifier 사용
    • 컴포넌트에 @qualifier("식별자명")으로 qualifire를 부여할 수 있음
    • 주입할 때도 앞에 @qualifier를 붙이면 해당 qualifire를 찾아서 주입해줌
@Componenct
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy impliments DiscountPolicy{}


public class Test{
    // Qualifier를 사용한 주입
    public OrderServiceImpl(@Qualifier("mainDiscountPolicy") DiscountPolicy){
        ...
    }
}
  • @Primary
    • 같은 타입의 빈이 여러개 일 때, primary인 빈이 우선권을 갖는다
    • 컴포넌트에 @Primary를 붙여 사용한다
  •  primary 보단 qualifier가 우선순위가 높음 (자동보단 수동, 넓은 범위 보단 좁은 범위가 우선)

 

Quailifier 어노테이션 만들기

@Qualifier("qualifier 명")은 qualifier 명이 문자기 때문에 컴파일 타임에서 오류가 잡히지 않는다. (qualifier 명을 오타 내도 잡을 수 없음)

이를 어노테이션을 직접 만들어서 해결할 수 있다.

qualifier 어노테이션을 들어가보면 위와 같이 구성돼있는데, 블록친 부분을 가져와서 직접 특정 Qualifier를 만들 수 있다.

@interface에 위에서 복사한 어노테이션들을 붙이고, @Qualifier("qualifier 명")을 붙여주자.

 

그리고 Qualifier를 사용할 때는 @Qualifier("mainDiscountPolicy") 대신 @MainDiscountPolicy를 붙여주면 된다.

 

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

List나 Map으로 특정 타입의 빈을 모두 주입받을 수 있다.

static class DiscountService {
    private final Map<String, DiscountPolicy> policyMap;
    private final List<DiscountPolicy> policies;

    // policyMap과 policies는 모든 DiscountPolicy를 주입받는다.
    // 만약 해당 타입의 빈이 없다면 빈 Map이나 컬렉션을 주입한다.
    public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
        this.policyMap = policyMap;
        this.policies = policies;
    }
}

댓글