반응형

@Primary 어노테이션이란?

스프링 프레임워크 3.0에서부터 제공하는 동일한 타입의 bean을 여러개 등록 후,
사용시 우선순위를 부여하기 위한 어노테이션

일반적인 bean 선언 후 사용 방법

public interface Car {
    void move();
}
public class Sedan implements Car {

    private String moveMessage;

    public Sedan(){
        this.moveMessage = "세단이 움직인다.";
    }

    public Sedan(String moveMessage){
        if(moveMessage == null) throw new NullPointerException("move message can not be empty");
        this.moveMessage = moveMessage;
    }

    @Override
    public void move() {
        System.out.println(moveMessage);
    }
}
public class SportsCar implements Car{

    private String moveMessage;

    public SportsCar(){
        this.moveMessage = "스포츠카가 움직인다.";
    }

    public SportsCar(String moveMessage){
        if(moveMessage == null) throw new NullPointerException("move message can not be empty");
        this.moveMessage = moveMessage;
    }

    @Override
    public void move() {
        System.out.println(moveMessage);
    }
}

위와 같이 Car 인터페이스를 구현하는 구현체 SportsCar, Sedan이라는 클래스가 있다고 가정하자.

이 클래스들의 객체를 bean으로 선언해 스프링 컨테이너로 관리하고 싶다고 했을 때

다음과 같이 bean으로 등록할 수 있을 것이다.

@Configuration
public class CarConfiguration {

    @Bean(name = "sedan")
    public Car carBuilder(){
        return new Sedan();
    }

    @Bean(name = "sportsCar")
    public Car sportsCarBuilder(){
        return new SportsCar();
    }

}

위에서 선언한 bean들을 각각 사용하려면 2가지 방법을 사용할 수 있을 것이다.

 

1. 주입할 객체의 이름을 기존에 선언한 bean의 이름과 똑같이 선언하기

@RestController
public class CarController {

    private Car car;

    public CarController(/*주입 대상 bean 이름*/Car sedan){
        this.car = sedan;
    }

    @GetMapping("/move/car")
    public String moveCar(){
        car.move();
        return "차가 움직였습니다.";
    }

}

2. 1과 동일하지만 필드의 이름을 bean과 같은 이름으로 사용하지 않더라도, 필드에 @Qualifier 어노테이션으로 선언한 bean의 이름을 명시해주기

@RestController
public class CarController {

    @Qualifier("sedan")
    @Autowired
    private Car car;

    @GetMapping("/move/car")
    public String moveCar(){
        car.move();
        return "차가 움직였습니다.";
    }

}

동일한 타입의 bean이 여러 개가 선언되어 있을 때 위와 같이 처리하지 않으면,
스프링 애플리케이션을 실행시에 NoUniqueBeanDefinitionException이 발생한다.


동일한 타입의 bean이 여러개가 있을 때 필드 주입시 별다른 설정 없이 기본 객체를 두고 사용하고 싶다면?

-> @Primary 어노테이션 사용하기

위에서 살펴본 예제에서 다음과 같이 조금만 바꿔보자.

@Configuration
public class CarConfiguration {

    @Bean(name="sedan")
    public Car carBuilder(){
        return new Sedan();
    }

    @Primary
    @Bean(name="sportsCar")
    public Car sportsCarBuilder(){
        return new SportsCar();
    }

}

@RestController
public class CarController {

    private Car car;

    public CarController(Car car){
        this.car = car;
    }

    @GetMapping("/move/car")
    public String moveCar(){
        car.move();
        return "차가 움직였습니다.";
    }

}

@Primary 어노테이션을 붙이면 컴포넌트에서 필드를 신경쓰지 않고 주입하더라도 어플리케이션이 정상적으로
동작하며, @Primary 어노테이션이 붙은 객체로 작동하는 것을 볼 수 있다.

또한, @Primary 어노테이션을 동일한 타입의 bean에 2개 이상 붙이게 되면 NoUniqueBeanDefinitionException가 발생하게 된다.


주의사항!

여기까지가 다른 블로그를 참고하더라도 알 수 있는 내용이었다.

그런데 @Primary 어노테이션을 2개 이상 붙이더라도 어플리케이션 에러가 발생하지 않는 두가지 상황이 존재한다.

1. 해당 타입의 선언된 여러개의 bean들을 하나도 한번도 사용하지 않는 경우

  • bean이 등록되어 있으니까 이제 사용해볼까 주입하는 순간 어김없이 에러 발생

2. 해당 bean들 중 사용하는 bean을 @Qualifier 어노테이션을 활용해 주입한 경우

  • 그 bean의 기능을 사용하더라도 기능이 정상적으로 동작함.
  • 그 이외로 bean을 주입해 사용하려는 순간 또 어김없이 에러 발생

이것때문에 필자가 복붙으로 bean을 생성하고 작업을 하다가 갑자기 애플리케이션이 뜨지 않는 상황이 생기니

당황스러웠었다.

원인은 @Primary 어노테이션이 두개 이상 붙어 있었는데 bean들을 @Qualifier 어노테이션을 붙여 선언하니 기존에는 웹이 정상적으로 떴었던 것이다.


결론

  • @Primary 어노테이션은 동일한 타입당 한번만 사용해야 한다.
  • 원래는 됐는데 왜 NoUniqueBeanDefinitionException 예외가 발생하지? 라는 의문이 들때
    원래 동작하던 변수들은 @Qualifier 어노테이션을 가지고 있지는 않았는지 확인해보자.
반응형