@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 어노테이션을 가지고 있지는 않았는지 확인해보자.
'개발 > spring' 카테고리의 다른 글
[Spring] pojo 기반의 설정 객체 사용 방법 (0) | 2024.02.18 |
---|---|
[spring] application event 사용해보기 (1) | 2024.02.05 |
[Spring/MongoDB] MongoDB에 Entity를 저장할 때 필요 없는 필드 제외하기 - 2 (0) | 2021.09.01 |
[Spring/MongoDB] MongoDB에 Entity를 저장할 때 필요 없는 필드 제외하기 (0) | 2021.06.22 |
[Spring Cloud/MSA] 1. Spring Cloud Eureka Server, Client 사용하기 (0) | 2021.06.07 |