Fine-tuning Annotation-based Autowiring with @Primary or @Fallback

2023. 7. 8. 19:06Spring Framework/Spring IoC

이 섹션에서는 “다중 후보 빈 중에서 특정 빈을 우선적으로 선택”해야 하는 상황에서 Spring이 제공하는 미세 조정(fine-tuning) 기능을 소개하고 있습니다.
과거에는 주로 @Primary(스프링 3.0+)만 알려졌지만, Spring 6.2 버전부터는 새로운 @Fallback 애노테이션도 도입되어, 주입 우선순위를 더욱 세밀하게 제어할 수 있게 되었습니다.

1) 왜 필요한가?

  • 스프링에서 “타입에 따른 자동 주입”(@Autowired, @Inject 등)을 쓸 때, 같은 타입을 구현하는 여러 개의 Bean이 등록되어 있으면 스프링은 “어느 한 Bean”을 선택해야 합니다.
  • 만약 명시적인 지정(@Qualifier)이나 대표 Bean(@Primary) 지정이 없으면, NoUniqueBeanDefinitionException 예외가 발생할 수 있습니다.

예:

@Configuration
public class MovieConfiguration {

    @Bean
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }
}

public class MovieRecommender {
    @Autowired
    private MovieCatalog movieCatalog; // <-- 같은 타입이 2개라 충돌
}

위 경우, MovieCatalog가 2개 있으므로 하나만 주입할 수가 없어서 충돌이 일어납니다.
이를 해결하기 위해 Spring은 @Primary, @Qualifier, @Fallback 같은 메커니즘을 제공합니다.

2) @Primary 애노테이션

  • 가장 전통적인 방법: 만약 한 빈을 유일한 대표(“primary”)로 삼고 싶다면, 그 빈 정의에 @Primary를 붙입니다.
  • 스프링은 여러 후보 중 @Primary가 붙은 빈을 우선 선택해서 주입합니다.

예:

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() {
        return new FirstMovieCatalog();
    }

    @Bean
    public MovieCatalog secondMovieCatalog() {
        return new SecondMovieCatalog();
    }
}
  • 이 설정에서는 MovieCatalog를 구현한 Bean이 2개 있더라도, firstMovieCatalog()가 @Primary로 지정되어 있으므로, MovieRecommender나 다른 클래스에서 @Autowired MovieCatalog를 단일 필드로 주입할 때 무조건 firstMovieCatalog가 선택됩니다.

3) 새롭게 등장한 @Fallback (Spring 6.2+)

  • Spring 6.2 버전부터 새롭게 추가된 애노테이션
  • @Primary는 “내가 대표 Bean이다”라는 적극적 의미라면, @Fallback은 “나는 서브(대체) Bean이니, 다른 정규(regular) Bean이 없으면 그때만 나를 사용”이라는 소극적 의미로 작동합니다.
  • 구체적으로:
    • “Regular” Bean: @Fallback가 붙어있지 않은 Bean (즉, 일반적인 Bean)
    • “Fallback” Bean: @Fallback가 붙은 Bean
  • 만약 어떤 타입의 Bean이 “Regular”와 “Fallback”를 섞어서 여러 개 있을 경우,
    1. 먼저 “Regular” Bean 중에서 여러 개가 있다면, 여전히 충돌(NoUniqueBeanDefinitionException) 혹은 @Primary 빈을 선택
    2. 만약 “Regular” Bean이 정확히 하나만 존재한다면, 그게 곧 주입 대상이 되고, “Fallback” Bean은 무시
    3. 만약 “Regular” Bean이 아예 없거나 여러 개 중에서 조건이 맞지 않는다면, 그때 “Fallback” Bean이 주입 후보가 될 수 있음(필요 시 하나만 남아야 함)

예:

@Configuration
public class MovieConfiguration {

    @Bean
    public MovieCatalog firstMovieCatalog() { 
        return new FirstMovieCatalog();
    }

    @Bean
    @Fallback
    public MovieCatalog secondMovieCatalog() {
        return new SecondMovieCatalog();
    }
}
  • 여기서 firstMovieCatalog()는 “regular Bean”, secondMovieCatalog()는 “fallback Bean”이 됩니다.
  • @Autowired private MovieCatalog movieCatalog; 주입 시,
    • “regular” Bean이 1개이므로( firstMovieCatalog ), 스프링은 그걸 주입 대상으로 선택
    • “fallback”인 secondMovieCatalog는 후보에 들지 않음
  • 만약 @Bean에서 firstMovieCatalog()가 사라지거나, @Conditional 등에 의해 등록되지 않았다면, 이제 “regular Bean”이 없으므로 “fallback Bean”인 secondMovieCatalog가 사용됩니다.
  • 즉, “regular”가 있으면 그걸 쓰고, 없으면 “fallback”을 쓰는 전략입니다.
● 이는 “서브(대체) Bean”을 등록해두되, 정상 상황에서는 쓰지 않고, 정규 Bean이 없을 때만  대신 쓰고자 하는 시나리오에 적합합니다.
● 예: 특정 라이브러리가 없는 환경에서만 대체 구현체를 제공하는 경우 등.

4) 공통점: MovieRecommender에 단일 필드 주입 → 한 Bean만 선택

문서의 예시:

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...
}
  • @AutowiredmovieCatalog 필드에 MovieCatalog 타입의 구현체가 2개 이상 존재하면 충돌
  • @Primary 붙은 게 1개면 자동으로 그걸 선택
  • @Fallback도 마찬가지로, “regular”와 “fallback” 조합에 따라 1개만 선택되도록 동작

공식 문서에서 말하듯이,

"With both variants of the preceding configuration, the following MovieRecommender is autowired with the firstMovieCatalog."

즉, firstMovieCatalog가 주입된다는 것은, 두 가지 경우가 동일하게 작동함을 의미합니다.

 

5) 실제 선택 로직 비교

  1. @Primary 방식:
    • 다수 후보 중 @Primary가 붙은 Bean이 “무조건 우선순위 1위”
    • “대표 Bean”이라는 개념
    • 만약 @Primary 붙은 Bean이 여러 개면 충돌
  2. @Fallback 방식(Spring 6.2+):
    • 우선 "fallback 아닌 Bean들(Regular)" 중에서 하나만 남으면 그걸 선택
    • Regular가 여러 개면 충돌(NoUniqueBeanDefinitionException), @Primary가 있으면 그걸 선택
    • Regular가 하나도 없거나 등록이 안 되었다면, 그제서야 Fallback Bean 후보들 중 하나를 선택
    • 복수의 @Fallback Bean이 있으면 다시 또 충돌 발생 가능(NoUniqueBeanDefinitionException)

즉, @Fallback은 “정규 빈이 존재하지 않을 때만” 사용되는 구조입니다.

6) 요약

  • @Primary: “우선순위가 가장 높은(대표) Bean”을 지정하는 고전적인 방법. 다중 후보 중 하나를 강제 선택할 때 사용
  • @Fallback (Spring 6.2+): “이 Bean은 일반 후보가 없을 때만” 사용하도록 표시해두는 애노테이션.
    • 정규(regular) Bean이 하나도 없으면 fallback Bean이 대신 주입
    • 정규 Bean이 존재하면 fallback Bean은 무시
  • 둘 다 “다중 후보 빈 중 하나를 선택”하는 문제를 해결하는데, 의도와 시나리오가 조금 다릅니다.
  • 문서 예제에서 보인 것처럼, firstMovieCatalog()가 @Primary이거나, secondMovieCatalog()가 @Fallback이면 결과적으로 firstMovieCatalog가 우선적으로 주입되어, MovieRecommenderfirstMovieCatalog Bean을 참조하게 됩니다.