[Spring] @Autowired 에서 조회되는 빈이 2개 이상이라면 ?? (@Primary, @Qualifier)

728x90

 

@Autowired 에서 조회되는 빈이 2개 이상일 때

@Autowired

- 타입을 기준으로 조회하기 때문에 같은 타입의 빈이 2개 이상이면 `@NoUniqueBeanDefinitionException` 오류가 발생한다.

 

조회 대상 빈이 2개 이상일 때 해결 방법

- @Autowired 필드명 또는 파라미터명을 이용한 매칭 (거의 사용 X)

- @Qualifier 를 이용한 명확한 구분 

- @Primary 를 이용한 기본 빈 설정

 

@Autowired 필드명 또는 파라미터명을 이용한 매칭 (거의 사용 X)

- 타입 매칭 시도 > 필드명 / 파라미터 명으로 매칭 시도

@Autowired
private DiscountPolicy rateDiscountPolicy

 

@Qualifier 를 이용한 명확한 구분

- 추가적인 구분자를 붙여주는 방법 (실제 빈 이름 변경 X,  단순한 문자열 태그 개념)

@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 를 붙여주고,

주입 시에 @Qualifier 와 함께 등록한 이름을 적어준다.

 

만약 @Qualilfier("등록한 이름") 을 찾지 못하면 등록한 이름을 가진 스프링 빈을 찾는다. 

(위에 코드에서 보면, 빈 이름이 mainDiscountPolicy 인 스프링 빈을 찾는다.)

하지만 의도를 명확히 하기 위해 빈 이름 대신 @Qualifier를 직접 지정하는 것이 좋다.

 

단점은 모든 코드에 @Qualifier 를 붙어야 한다는 점이다.

 

> 참고 <

- @Qualifier 는 직접 스프링 빈을 등록할 때도 사용할 수 있다.

 

@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;
}

 

@Primary + @Qualifier

- @Primary는 기본 선택, @Qualifier는 명확한 우선순위 지정한다.

- 두 개를 조합하면 더욱 깔끔한 코드 작성 가능하다.

@Component
@Primary
public class MainDatabaseConnection implements DatabaseConnection {}

@Component
@Qualifier("subDB")
public class SubDatabaseConnection implements DatabaseConnection {}

@Autowired
private DatabaseConnection databaseConnection; // @Primary가 적용된 빈이 자동 주입

@Autowired
public void setDatabaseConnection(@Qualifier("subDB") DatabaseConnection databaseConnection) {
    this.databaseConnection = databaseConnection; // 특정 빈을 명확히 주입
}

 

우선순위

@Primary : 기본값처럼 동작

@Qualifier : 매우 상세하게 동작 (우선순위 높음)

 

> 참고 <

- 일반적으로 스프링은 자동보다는 수동이, 넓은 범위의 선택권보다는 좁은 범위의 선택권이 우선순위가 높다.

 

애노테이션 직접 만들기

@Qualifier("이름") 의 문제점

- 컴파일 시 타입 체크 할 수 없다.

- 이름은 문자열이므로 오타가 발생해도 컴파일 오류가 발생하지 않는다.

 

따라서 커스텀 애노테이션을 만들면 안정성을 높일 수 있다.

 

애노테이션 생성 방법

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}

@Qualifier를 포함한 새로운 애노테이션을 생성한다.

 

`@Target` : 애노테이션 적용 위치 지정

`@Retention(RetentionPolicy.RUNTIME)` : 런타임시에도 유지되도록 설정

`@Qualifier("이름")` : Quilfier 역할 수행

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
  this.memberRepository = memberRepository;
  this.discountPolicy = discountPolicy;
}

 

스프링 빈을 등록할 때 만든 커스텀 애노테이션을 붙이면

@Autowired 주입 시 @MainDiscountPolicy 를 사용하여 안전한 타입 체크가 가능해진다.

 

 

 

 

 

 

 

 

출처 | 스프링 핵심 원리 - 기본편(김영한) - 인프런

728x90