본문 바로가기

spring

자동 주입시 빈이 2개 이상일 때 문제 해결

@Autowired 는 타입으로 빈을 조회한다.

또, 스프링의 객체지향적 인 코드를 짜기 위해 우리는 인터페이스와 스프링 컨테이너를 이용하여 DIP, OCP 를 만족시킨다.

 

그런데, 스프링에서 @Autowired 로 빈을 주입하기 위해 getBean 메서드와 같이 빈을 조회할 때에는 생성자, 수정자 주입시 매개변수의 파라미터, 필드 주입시 필드의 타입을 보고 해당 타입의 하위 타입을 모두 가져오게 된다.

 

그런데 만약 하위 타입들 중 @Component 로 등록된게 2개 이상이라면?

스프링은 오류를 뱉게 된다.

 

 

 

예를 들어 아래와 같은 서비스의 계층구조가 있다고 생각해보자.

그런데 AutoDrivingService, UserDrivingService 가 모두 빈에 등록된 것이다.

 

코드로 보면 대략 이럴 것이다.

 

@Component
class AutoDrivingService implements DrivingService {
...
}

@Component
class UserDrivingService implements DrivingService {
...
}

 

그리고 이 서비스를 사용하는 컨트롤러에는 대략 이런 코드가 적혀있을 것이다.

인터페이스를 사용하여 DIP, OCP 를 지키기 위해.

 

@Component
class DrivingController {
	
    private DrivingService drivingService;
    
    @Autowired
    public DrivingController(DrivingService drivingService) {
    	this.drivingService = drivingService;
    }
    
    ...
}

 

스프링은 DrivingService 타입의 하위 타입인 AutoDrivingService 와 UserDrivingService 중 어떤 빈을 컨트롤러에 등록해야할지 몰라

NoUniqueBeanDefinitionException 에러를 뱉게 된다.

 

이 상황에 대한 해결법을 공부해보았다.

 

 

 

1. @Autowired 을 지정한 곳에서 파라미터명(필드명)을 사용할 빈과 일치시킨다.

@Autowired 는 타입으로 빈을 조회할때 매칭한다고 했다.

그런데 여기서 기능이 더 있는게, 조회한 빈이 여러개일 경우에는 파라미터의 이름(생성자, 수정자 주입시) 또는 필드의 이름 (필드 주입시) 으로 한번더 추가로 매칭을 진행한다.

 

따라서 위의 컨트롤러 코드에서 만약 AutoDrivingService 빈을 주입시키고 싶다면 아래와 같이 파라미터의 이름을 빈의 이름과 일치시키면 된다.

 

@Component
class DrivingController {
	
    private DrivingService drivingService;
    
    @Autowired
    public DrivingController(DrivingService autoDrivingService) { // 이름을 일치시킨다.
    	this.drivingService = autoDrivingService;
    }
    
    ...
}

 

결과적으로 @Autowired 의 다음과 같은 매칭방법을 이용하는 것이다.

1. 타입으로 빈을 조회

2. 타입으로 조회한 결과가 여러개일 때, 이름으로 빈을 조회

 

 

 

2. @Qualifier 사용

@Qualifier 에너테이션은 추가적인 구분자를 넣어준다.

아래와 같이 사용한다.

 

먼저 빈등록 부분에 @Qualifier를 이용하여 다음과 같이 붙여준다.

@Component
@Qualifer("drivingServiceToUse")
class AutoDrivingService implements DrivingService {
...
}

@Component
class UserDrivingService implements DrivingService {
...
}

 

 

이후 빈을 주입하는 부분에서도 @Qualifier를 이용하여 매칭한다.

 

@Component
class DrivingController {
	
    private DrivingService drivingService;
    
    @Autowired
    public DrivingController(@Qualifier("drivingServiceToUse") DrivingService drivingService) {
    	this.drivingService = drivingService;
    }
    
    ...
}

 

@Qualifier 가 빈의 이름을 따로 바꿔주는 것은 아니며, 단지 매칭할 기준을 정해준다고 생각하면 된다.

 

@Qualifier 도 @Autowired 처럼 매칭되는 @Qualifier 를 찾지 못하면, 옵션에 등록된 문자열과 같은 이름의 빈을 찾는다.

하지만 이를 이용하는 것은 추천되는 방법은 아니고, 반드시 매칭할 빈과 빈을 주입하는 곳 둘 다 @Qualifier로 연결시키는 것이 좋다.

 

 

사용자 정의 에너테이션 활용하기

@Qualifier 같이 옵션으로 문자열을 주는 에너테이션은 오타가 나면 발견하기 쉽지 않다.

컴파일타임에서 오류를 내주지 않기 때문이다.

 

따라서 사용자정의 에너테이션을 만들어 사용하면 편리하다.

 

/**
 * 문자가 들어간 에너테이션은 오타나도 컴파일타임에 알 수 없다.
 * 따라서 이렇게 커스텀 에너테이션을 만들어 쓰면 좋을 때가 있다.
 *
 * 에너테이션은 자바 기능으로서는 상속이 없다. @Qualifier 가 있네 하며 기능해주는것은
 * 스프링이 해주는 것이다.
 */
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("drivingServiceToUse")
public @interface MainDrivingService {
}
@Component
class DrivingController {
	
    private DrivingService drivingService;
    
    @Autowired
    public DrivingController(@MainDrivingService DrivingService drivingService) {
    	this.drivingService = drivingService;
    }
    
    ...
}

 

 

 

 

 

 

3. @Primary 사용

이 에너테이션은 정말 편리하다. 이름에서도 유추할 수 있듯이,

여러개의 등록된 빈 중에 우선순위를 주는 것이다.

사용할 빈에다가 이 에너테이션을 붙이기만 하면, 인터페이스로 주입해도 그 빈이 주입된다.

 

@Component
@Primary
class AutoDrivingService implements DrivingService {
...
}

@Component
class UserDrivingService implements DrivingService {
...
}
@Component
class DrivingController {
	
    private DrivingService drivingService;
    
    @Autowired
    public DrivingController(DrivingService drivingService) { // Auto 가 주입된다.
    	this.drivingService = drivingService;
    }
    
    ...
}

 

 

 

 

 

 

 

 

 

보통 코드를 직접 바꾸는 1번 방식보다는 2, 3번이 좋아보인다.

또, 빈 등록부분과 의존성 주입 부분 모두 적어줘야 하는 2번보다는 한번만 적어주면 되는 3번이 좋아보인다.

 

하지만 2, 3번의 에너테이션이 같이 있다면 우선순위는 2번 @Qualifier 가 가진다.

@Qualifier 는 상세하게 동작하고, @Primary 는 기본값처럼 동작하기 때문이다.

 

따라서 기본적으로 주로 사용하는 빈에 @Primary 를 붙이고, 비교적 덜 자주 사용되는 빈이 사용될 때 @Qualifer 를 붙여서 사용하도록 하자.

'spring' 카테고리의 다른 글

빈 스코프  (0) 2021.03.21
스프링 빈의 생명주기와 초기화 분리  (0) 2021.03.20
생성자 자동 주입의 장점, @RequiredArgsConstructor  (0) 2021.03.19
@Autowired  (0) 2021.03.18
@ComponentScan  (0) 2021.03.18