Spring Boot

@Autowired와 @Qualifier, @Primary

작은별._. 2023. 12. 5. 21:38
728x90

 

아래 포스팅에서 @Autowired의 사용법에 대해서 살펴보았습니다. 


 

@Autowired로 의존관계 주입하기

앞 포스팅(@ComponentScan으로 스프링 빈 스캔하기)에서 @Component와 @ComponentScan을 이용하여 스프링 빈을 스프링 컨테이너에 등록하였습니다. 이제는 @Autowired를 이용하여 의존관계를 자동으로 주입할

silver-programmer.tistory.com

 

 

위 포스팅에서도 언급했듯이, @Autowired는 타입(Type)으로 빈을 조회합니다. 이때, 특정 타입의 빈 클래스 (위 포스팅에서는 Product 인터페이스)를 상속하는 자식들이 여러 개 있고, 그 자식들도 스프링 빈으로 등록되어 있을 때, 자식들은 부모 타입이므로 부모 타입으로 빈을 조회하면 빈이 2개 이상이 되는 문제가 발생합니다.

 

한 번 코드로 확인해 보겠습니다. Product 인터페이스의 하위 타입인 Car, Computer 클래스 둘 다 스프링 빈으로 선언해 보겠습니다.


@Component
public class Car implements Product{}

@Component
public class Computer implements Product{}

 

 

그 후, 아래와 같이 의존관계 자동 주입을 실행한 후,


@Component
public class OnlineOrder implements Order {
    private  Product product;
    
    @Autowired // @Autowired 생략 가능
    public OnlineOrder(Product product) {
        this.product = product;
    }
}

 

 

테스트 코드를 돌려보았습니다.


   @Test
   @DisplayName("@Autowired 중복 처리 테스트")
   void AutoWiredTest(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(HelloApplication.class);
        ac.getBean(OnlineOrder.class);
   }

 

 

그럼 아래와 같이 에러가 발생하며 테스트가 실패하게 됩니다.


Error creating bean with name 'onlineOrder': Unsatisfied dependency expressed through field 'product': No qualifying bean of type 'com.example.hello.product.Product' available: expected single matching bean but found 2: car,computer

 

 

오류 메시지는 하나의 빈을 기대했는데, car, computer 2개가 발견되었다고 알려주고 있습니다. 이런 상황에서 @Autowired를 사용할 때 어떻게 해결할 수 있을지 확인해 보겠습니다. 

 

해결방법은 아래와 같이 3가지가 있습니다.

  • @Autowired 필드 명 매칭
  • @Qualifer 사용
  • @Primary 사용

이번 포스팅에서 각각에 대해서 알아보겠습니다.


@Autowired 필드 명 매칭

@Autowired는 기본적으로 타입(type) 매칭을 시도하는데, 이때 타입이 같은 빈이 여러 개가 있으면 필드 이름/파라미터 이름으로 빈 이름을 매칭하려고 합니다.

 

따라서, 주입받기를 원하는 빈의 클래스 이름으로 필드 명/파라미터 명으로 변경하면 됩니다. 아래와 코드를 통해 확인할 수 있습니다.


@Component
public class OnlineOrder implements Order {

    private Product computer;

  // @Autowired 생략 가능
    public OnlineOrder(Product computer) {
        this.computer = computer;
    }
    public Product getProduct(){
        return computer;
    }
}

 

 

위에서 사용한 테스트 코드를 한 번 더 돌려보면 잘 통과가 되는 것을 확인할 수 있습니다.

 

즉, @Autowired는

  1. 타입 매칭
  2. 타입 매칭의 결과가 2개 이상일 때 필드 명/파라미터 명으로 빈 이름 매칭

단계로 빈을 찾아나가게 되는 것으로 정리할 수 있습니다.


@Qualifier 사용

@Qualifier는 추가 구분자를 붙여주는 방법입니다. 즉, 빈 등록 시 @Qualifier를 붙여서 @Qualifier 내부에 원하는 구분자를 작성한 뒤, 그 구분자를 주입 시에 명시하는 방법입니다. 바로 코드로 확인해 보겠습니다.

 

아래와 같이 빈으로 등록할 클래스 위에 @Qualifier(구분자)를 작성합니다.


@Component
@Qualifier("computer")
public class Computer implements Product{}

@Component
@Qualifier("car")
public class Car implements Product{}

 


그 후 주입을 할 때 아래와 같이 @Qualifier(구분자)를 명시해 줍니다. 이번에는 OfflineOrder 클래스에 주입해 보겠습니다.


@Component
public class OfflineOrder implements Order {
    private Product product;

  // @Autowired 생략 가능
    public OfflineOrder( @Qualifier("car") Product product) {
        this.product = product;
    }

    public Product getProduct(){
        return product;
    }
}

 

그 후, 아래와 같이 테스트 코드를 만들어서 돌려보았더니 잘 통과하는 것을 확인할 수 있습니다.


   @Test
   @DisplayName("@Autowired 중복 처리 테스트")
    void AutoWiredTest(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(HelloApplication.class);
        List<Order> orderList = new ArrayList<>();
        orderList.add(ac.getBean(OnlineOrder.class));
        orderList.add(ac.getBean(OfflineOrder.class));

        for(Order order: orderList){
            System.out.println(order.getProduct().toString());
        }
   }

[출력 결과]

com.example.hello.product.Computer@7957dc72
com.example.hello.product.Car@6ab72419

 

 

OnlineOrder 빈은 Computer 타입의 빈을, OfflineOrder 빈은 Car 타입의 빈을 가지고 있는 것을 확인할 수 있습니다.

 

만약, @Qualifier로 주입할 때, @Qualifier("car")을 못 찾으면 어떻게 될까요? 그러면 이름이 car인 스프링 빈을 추가로 찾습니다. 하지만 이런 일이 발생하지 않도록 @Qualifier는 @Qualifier를 찾는 용도로만 사용하는 것이 명확하고 좋습니다.

 

해당 방법은 @Bean을 이용하여 직접 빈을 등록할 때도 사용할 수 있습니다.


@Bean
@Qualifier("myBean")
public MyBean myBean() { return new ...}

 

 

@Qualifier가 빈을 찾는 방법을 정리하면 아래와 같습니다.

  1. @Qualifier끼리 매칭
  2. 빈 이름 매칭
  3. NoSuchBeanDefinitionException 발생

@Primary 사용

마지막으로 @Primary를 사용하여 중복되는 빈을 해결해 보겠습니다. @Primary는 우선순위를 정하는 방법으로, @Autowired 시에 여러 빈이 매칭되면 @Primary가 붙은 빈이 우선권을 가지게 됩니다. 코드를 통해 확인해 보겠습니다.

 

Car 클래스에 @Primary를 붙여주겠습니다.


@Component
@Qualifier("car") // @Qualifier는 주석처리해도 괜찮습니다.
@Primary
public class Car implements Product{}

@Component
@Qualifier("computer") // @Qualifier는 주석처리해도 괜찮습니다.
public class Computer implements Product{}

그 후, 생성자 주입을 받아보겠습니다. OfflineOrder, OnlineOrder 클래스 코드 모두를 그대로 두고, 위와 동일한 테스트 코드만 돌려보겠습니다. 즉, 아래 코드를 실행해 보았습니다.


// 위와 동일
@Component
public class OnlineOrder implements Order {
    private Product computer;

    public OnlineOrder(Product computer) {
        this.computer = computer;
    }
    public Product getProduct(){
        return computer;
    }
}


@Component
public class OfflineOrder implements Order {
    private Product computer;

   // 해당 코드를 사용하려면 Car 클래스에 @Qualifier 명시해 주세요
    public OfflineOrder( @Qualifier("car") Product computer) {
        this.computer = computer;
    }

    public Product getProduct(){
        return computer;
    }
}

// 테스트 (위와 동일)
@Test
@DisplayName("@Autowired 중복 처리 테스트")
void AutoWiredTest(){
      ApplicationContext ac = new AnnotationConfigApplicationContext(HelloApplication.class);
      List<Order> orderList = new ArrayList<>();
      orderList.add(ac.getBean(OnlineOrder.class));
      orderList.add(ac.getBean(OfflineOrder.class));

      for(Order order: orderList){
         System.out.println(order.getProduct().toString());
      }
  }

[출력 결과]

 

com.example.hello.product.Car@3aacf32a
com.example.hello.product.Car@3aacf32a

 

 

OnlineOrder의 변수명이 computer임에도 불구하고, Car 클래스가 우선권을 가지므로 Car 타입의 빈이 주입되었음을 확인할 수 있습니다.


 

@Primary vs @Qualifier

 

만약, @Primary와 @Qualifier 가 충돌할 경우에는 어떤 것이 우선권을 가져갈까요? 스프링은 자동보다는 수동이, 넓은 범위의 선택권보다는 좁은 범위의 선택권이 우선순위가 높습니다. 따라서, 여기서도 @Qualifier가 우선권이 높습니다. 


 

여기까지가 @Autowired를 이용하여 중복되는 타입의 빈 충돌을 해결하는 방법이었습니다. 만약, 의도적으로 해당 타입의 스프링 빈이 다 필요한 경우라면, 즉 동적으로 Car, Computer 등의 클래스를 실행 중에 선택해야 하는 경우라면 어떻게 해야 할까요? 이 때는 List나 Map과 같은 Collection을 사용하여 해결할 수 있습니다. 이 부분에 대해서는 아래 포스팅에서 확인할 수 있습니다.

 

 

@Autowired와 Collection 클래스(List, Map, etc.)

아래 포스팅에서 동일 타입의 빈들이 충돌할 때 어떻게 해결할 수 있는지 확인했습니다. 이번 포스팅은 의도적으로 해당 타입의 스프링 빈이 다 필요한 경우, 특히 동적으로 동일 타입의 스프링

silver-programmer.tistory.com

 

감사합니다!


[전체 소스코드]

 

GitHub - eunhwa99/SpringBlog

Contribute to eunhwa99/SpringBlog development by creating an account on GitHub.

github.com

 

 

[참고자료]

김영한, "스프링 핵심 원리 - 기본편", 인프런

 

 

 

728x90
반응형