Fine-tuning Annotation-based Autowiring with Qualifiers

2024. 11. 14. 14:02Spring Framework/Spring IoC

아래 설명에서는 Spring의 Qualifier 활용과 @Autowired 주입 방식에 대해 자세히 살펴보고, 공식 문서 예제에 제시된 XML 기반 설정을 자바 기반 구성(Java Configuration)으로 변환하는 방법을 함께 안내합니다.

 

1. 개요: @Autowired와 @Qualifier

스프링에서 @Autowired는 타입(type) 중심으로 의존성을 주입합니다. 그러나 같은 타입의 빈(bean)이 여러 개 존재하면, 어떤 빈을 주입해야 할지 모호해집니다. 이 때,

  • @Primary (또는 최근 버전에서는 @Fallback)
  • @Qualifier 와 같은 추가적인 메커니즘으로 주입 대상을 결정할 수 있습니다.

그중 @Qualifier는 여러 후보 빈 중에서 특정 빈을 추가 식별자로 정교하게 선택하는 데 활용됩니다. 자바 기반 구성에서는 @Bean 메서드나 직접 @Component에 @Qualifier를 붙여서 빈을 등록한 뒤, 주입받는 쪽에도 @Qualifier나 커스텀 애노테이션을 붙여 일치시키는 방식으로 사용합니다.

 

2. 기본 예시: 간단한 @Qualifier 사용

2.1 예제 클래스

// MovieCatalog.java
public interface MovieCatalog {
    // 영화 카탈로그 인터페이스
}

// SimpleMovieCatalog.java
public class SimpleMovieCatalog implements MovieCatalog {
    // 실제 구현
}

// MovieRecommender.java
public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // 혹은 컨스트럭터/메서드 주입 형태
    /*
    private final MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(@Qualifier("main") MovieCatalog movieCatalog) {
        this.movieCatalog = movieCatalog;
    }
    */
}

2.2 자바 기반 구성 코드

공식 문서에서는 XML <bean> 태그 안에 <qualifier value="main"/> 또는 <qualifier value="action"/> 과 같이 기술했지만, 자바 기반 구성에서는 @Bean 메서드에 @Qualifier로 표현할 수 있습니다.

@Configuration
public class AppConfig {

    // "main"이라는 식별자를 갖는 MovieCatalog 타입의 빈
    @Bean
    @Qualifier("main")
    public MovieCatalog mainMovieCatalog() {
        return new SimpleMovieCatalog();
    }

    // "action"이라는 식별자를 갖는 MovieCatalog 타입의 빈
    @Bean
    @Qualifier("action")
    public MovieCatalog actionMovieCatalog() {
        return new SimpleMovieCatalog();
    }

    // MovieRecommender는 @Autowired를 통해 mainMovieCatalog()를 주입받을 예정
    @Bean
    public MovieRecommender movieRecommender() {
        return new MovieRecommender();
    }
}

위 구성에서 @Qualifier("main")와 @Qualifier("action")을 붙인 @Bean들이 각각 SimpleMovieCatalog 빈을 생성합니다. MovieRecommender의 movieCatalog 필드는 @Qualifier("main")으로 지정된 빈이 주입됩니다.

 

3. 이름 매칭에 의한 자동 주입

Spring 6.1부터는 -parameters 컴파일 옵션이 활성화된 경우, 파라미터 이름이 빈 이름과 일치하면 자동으로 매칭을 시도합니다.

  • @Qualifier나 @Primary(또는 @Fallback) 등이 없는 상황에서, 의존성 주입 대상이 모호하면 필드/파라미터 이름과 동일한 이름의 빈을 우선적으로 찾습니다.

예를 들어, 다음과 같이 movieCatalog라는 이름으로 @Autowired 필드를 선언했다면, 빈 이름이 movieCatalog인 것이 유일하게 존재하면 그 빈이 자동 주입될 수 있습니다.

@Configuration
public class AppConfigWithName {

    @Bean
    public MovieCatalog movieCatalog() {
        return new SimpleMovieCatalog();
    }

    @Bean
    public MovieRecommender movieRecommender() {
        return new MovieRecommender();
    }
}

 

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;
    
    // ...
}

위 경우 별도 @Qualifier 없이도, 필드 이름 movieCatalog와 동일한 빈 이름을 가진 @Bean이 존재하면 매칭이 이루어집니다.

 

4. 커스텀 Qualifier 애노테이션 사용

@Qualifier("값")만으로도 빈을 식별할 수 있지만, 좀 더 의미 있는 컨텍스트 정보를 주입하거나, 여러 속성값을 사용하고 싶을 때 커스텀 Qualifier 애노테이션을 만들 수 있습니다.

4.1 단일 값(value) 커스텀 Qualifier

아래와 같이 @Qualifier를 내장한 애노테이션을 정의할 수 있습니다.

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
    String value();
}

이후 주입받는 코드에서 @Genre("Action")과 같이 사용합니다.

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;

    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...
}

자바 기반 구성에서의 사용

XML 예제에서는 <qualifier type="Genre" value="Action"/> 등을 <bean> 태그 내부에 썼지만, 자바 설정에서는 @Bean에 직접 @Genre("Action")로 표기할 수 있습니다.

@Configuration
public class GenreConfig {

    @Bean
    @Genre("Action")
    public MovieCatalog actionCatalog() {
        return new SimpleMovieCatalog();
    }

    @Bean
    @Genre("Comedy")
    public MovieCatalog comedyCatalog() {
        return new SimpleMovieCatalog();
    }

    @Bean
    public MovieRecommender movieRecommender() {
        return new MovieRecommender();
    }
}

이렇게 하면, @Genre("Action")이 붙은 빈은 @Genre("Action")으로 주입 요청되는 곳으로 연결됩니다. @Genre("Comedy")가 붙은 빈은 @Genre("Comedy")로 주입됩니다.

 

4.2 값(value) 없는 커스텀 Qualifier

어떤 경우에는 단순히 오프라인용 온라인용 등과 같이 존재 자체로 의미를 부여하는 커스텀 애노테이션을 쓸 수 있습니다. 예를 들어,

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}

그리고 주입받는 코드:

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...
}

자바 기반 구성

XML 예시에서는 <qualifier type="Offline"/>라고만 작성했지만, 자바 기반 구성에서는 다음과 같이 @Bean에 @Offline만 달면 됩니다.

@Configuration
public class OfflineConfig {

    @Bean
    @Offline
    public MovieCatalog offlineCatalog() {
        return new SimpleMovieCatalog();
    }

    @Bean
    public MovieRecommender movieRecommender() {
        return new MovieRecommender();
    }
}

 

 

5. 여러 속성을 가진 커스텀 Qualifier

하나의 커스텀 애노테이션에서 여러 속성값을 가질 수도 있습니다. 예를 들어, 다음과 같이 정의해 보겠습니다.

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
    String genre();
    Format format();
}
public enum Format {
    VHS, DVD, BLURAY
}

주입받는 클래스:

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format = Format.VHS,    genre = "Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format = Format.VHS,    genre = "Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format = Format.DVD,    genre = "Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format = Format.BLURAY, genre = "Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...
}

자바 기반 구성

공식 문서 예제에서는 <qualifier type="MovieQualifier"><attribute key="format" value="VHS"/><attribute key="genre" value="Action"/></qualifier> 와 <meta key="..."/> 등을 혼용했습니다. 자바 기반 구성으로 바꾸면 다음과 같이 매우 간단히 표현할 수 있습니다.

@Configuration
public class MultiAttributeQualifierConfig {

    // VHS & Action
    @Bean
    @MovieQualifier(format = Format.VHS, genre = "Action")
    public MovieCatalog actionVhsCatalog() {
        return new SimpleMovieCatalog();
    }

    // VHS & Comedy
    @Bean
    @MovieQualifier(format = Format.VHS, genre = "Comedy")
    public MovieCatalog comedyVhsCatalog() {
        return new SimpleMovieCatalog();
    }

    // DVD & Action
    @Bean
    @MovieQualifier(format = Format.DVD, genre = "Action")
    public MovieCatalog actionDvdCatalog() {
        return new SimpleMovieCatalog();
    }

    // BLURAY & Comedy
    @Bean
    @MovieQualifier(format = Format.BLURAY, genre = "Comedy")
    public MovieCatalog comedyBluRayCatalog() {
        return new SimpleMovieCatalog();
    }

    @Bean
    public MovieRecommender movieRecommender() {
        return new MovieRecommender();
    }
}

이렇게 @MovieQualifier(...)로 두 가지 속성을 모두 지정해두면, 주입받을 때 역시 같은 속성의 값들이 모두 일치해야 주입이 이뤄집니다.

 

6. @Resource와의 비교

  • @Resource(JSR-250)는 디폴트로 “이름(name)으로 매칭”하는 애노테이션입니다. 즉, 빈의 고유 이름으로 주입할 때 적합하고, 타입 매칭은 고려되지 않습니다(다만 동일 타입 발견 시 우선순위 모호성은 발생할 수 있음).
  • 반면 @Autowired는 “타입으로 매칭” 후, 추가로 @Qualifier의 값이나 빈 이름을 사용해 후보를 필터링하는 방식입니다.

단순히 “이 빈 이름을 정확히 주입해야 한다”라고 하면 @Resource(name="…") 방식이 더 직관적일 수 있으나, 여러 타입 혹은 Qualifier를 활용하는 구조라면 @Autowired+@Qualifier가 더 유연합니다.

추가로 @Autowired는 컨스트럭트나 여러 아규먼트를 갖는 메서드에도 사용 가능합니다. 반면 @Resource는 필드 또는 setter 메서드에만 적용되며, 컨스트럭터 주입과는 호환되지 않습니다.

 

7. 요약

  1. 디폴트로 @Autowired는 타입 매칭을 통해 의존성을 주입한다.
  2. 빈이 여러 개 존재하면,
    • @Primary(또는 스프링 6.0+에서는 @Fallback)
    • @Qualifier("…")
    • 파라미터/필드 이름 매칭 등의 방식으로 모호성을 제거한다.
  3. @Qualifier에 문자열 값을 주어 해당 식별자를 기준으로 후보 빈을 좁히거나, 커스텀 애노테이션을 만들어 좀 더 의미 있는 키(key)들을 활용할 수 있다.
  4. 자바 기반 설정에서는 @Bean 메서드에 @Qualifier(또는 커스텀 애노테이션)를 적용하여 XML <qualifier> 엘리먼트와 동일한 효과를 낼 수 있다.
  5. @Resource는 디폴트로 빈 이름 매칭(고유 식별자) 위주로 동작하며, 컨스트럭터 주입이나 멀티 아규먼트 메서드 주입에는 쓰이지 않는다.

정리하자면, XML 설정에서의 <qualifier>와 <meta> 등을 사용하던 부분을 자바 기반 구성에서는 @Bean 메서드에 @Qualifier 또는 커스텀 Qualifier를 붙여 간단하게 대체할 수 있습니다. 특히, 여러 속성이 필요한 경우에도 XML의 <attribute> 혹은 <meta> 태그 대신, 자바 애노테이션 속성으로 선언함으로써 더욱 직관적이고 간결하게 관리할 수 있습니다.

위 예제들을 참고하여 Java Configuration 환경에서 원하는 대로 Qualifier를 세밀하게 지정하고 주입받는 로직을 구성하시면 됩니다.

'Spring Framework > Spring IoC' 카테고리의 다른 글

Basic Concepts: @Bean and @Configuration  (0) 2024.11.14
Java-based Container Configuration  (1) 2024.11.14
Annotation-based Container Configuration  (0) 2024.11.14
Bean Scopes  (0) 2024.11.14
Method Injection  (0) 2024.11.14