본문 바로가기
백엔드 개발

Spring에서 @Autowired를 지양해야 하는 이유와No qualifying bean 오류가 발생하는 근본 원인

by collenkim 2026. 4. 7.
반응형

들어가며

Spring 개발을 하다 보면 아래 에러는 거의 반드시 한 번은 만나게 된다.

No qualifying bean of type 'xxx' available

 

이 에러를 단순히 “Bean이 없어서 발생한 문제”로 보면 해결이 어렵다.
실제로는 대부분 의존성 주입 방식이나 구조 설계 문제에서 시작된다.

특히 @Autowired 필드 주입을 사용하고 있다면,
이 에러가 더 쉽게 발생하고 원인 파악도 어려워진다.


No qualifying bean이 실제로 발생하는 상황

실무에서 가장 흔한 케이스는 인터페이스 기반 설계에서 발생한다.

public interface MyService { 
	void execute(); 
}

 

구현체가 여러 개인 경우

@Service 
public class AService implements MyService { 
	public void execute() {} 
} 

@Service 
public class BService implements MyService { 
	public void execute() {} 
}

 

이 상태에서 의존성을 주입하면 문제가 발생한다.

@Service 
public class OrderService { 
	private final MyService myService; 
    
    public OrderService(MyService myService) { 
		this.myService = myService; 
	} 
}

 

Spring 입장에서는 MyService 타입의 Bean이 두 개이기 때문에
어떤 것을 주입해야 할지 결정할 수 없다.

이때 발생하는 것이 바로 No qualifying bean 또는
expected single matching bean but found 2 계열의 오류다.


흔한 해결 방법과 한계

1. @Qualifier

@Service 
public class OrderService { 
	private final MyService myService; 
    
    public OrderService(@Qualifier("AService") MyService myService) { 
		this.myService = myService; 
	} 
}

 

특정 Bean을 지정할 수 있다.

하지만 한계가 명확하다.

  • 문자열 기반이라 리팩토링에 취약함
  • Bean 이름 변경 시 컴파일 에러 없음
  • 실수해도 런타임에서만 문제 발생

즉, 동작은 하지만 구조적으로 안전하지 않다.


실무에서 더 많이 사용하는 해결 방법

1. @Primary

@Service
@Primary 
public class AService implements MyService {
} 

@Service 
public class BService implements MyService {
}

 

기본 Bean을 지정하는 방식이다.

  • 단순하다
  • 유지보수가 쉽다
  • 실무에서 가장 많이 사용된다

2. 커스텀 Qualifier (타입 안정성 확보)

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

@Service
@MainService
public class AService implements MyService {}

@Service
public class OrderService {

    private final MyService myService;

    public OrderService(@MainService MyService myService) {
        this.myService = myService;
    }
}

 

문자열이 아니라 타입 기반으로 선택하기 때문에

  • 리팩토링에 안전
  • 실수 가능성 낮음

그럼 @Autowired는 왜 문제인가

필드 주입을 보면 문제가 더 명확해진다.

@Service 
public class OrderService { 
	@Autowired private MyService myService; 
    
 }

 

이 구조의 핵심 문제:

  • 의존성이 코드에 드러나지 않는다
  • 객체 생성 시 필요한 값이 명확하지 않다
  • 테스트 작성이 어렵다 (Spring Context 의존)
  • 문제 발생 시점이 늦다 (런타임)

즉, 문제를 숨기고 늦게 터뜨리는 구조


생성자 주입이 해결하는 핵심 포인트

@Service 
public class OrderService { 
	private final MyService myService; 

	public OrderService(MyService myService) { 
    	this.myService = myService; 
    } 
}

 

 

이 경우 Spring은 객체 생성 시점에
반드시 의존성을 해결해야 한다.

 

즉,

  • 애플리케이션 시작 단계에서 바로 실패
  • 문제 위치가 명확
  • 잘못된 설계를 초기에 차단

@RequiredArgsConstructor로 완성되는 생성자 주입

생성자 주입의 단점은 코드가 길어진다는 점이다.

이걸 해결하는 것이 @RequiredArgsConstructor다.

@Service
@RequiredArgsConstructor
public class OrderService {

    private final MyService myService;
}

 

이 어노테이션은

  • final 필드만을 대상으로
  • 생성자를 자동 생성해 준다

즉, 아래 코드와 완전히 동일하다.

public OrderService(MyService myService) {
    this.myService = myService;
}

핵심 포인트

  • 코드 길이는 줄어든다
  • 생성자 주입의 장점은 그대로 유지된다
  • 실수 여지도 줄어든다

결과적으로

생성자 주입을 기본값처럼 사용할 수 있게 만들어준다

실무에서 체감되는 차이

필드 주입을 사용하면

  • 문제가 나중에
  • 예상하지 못한 위치에서 발생

생성자 주입(+ @RequiredArgsConstructor)을 사용하면

  • 문제가 처음에
  • 정확한 위치에서 발생

프로젝트가 커질수록
이 차이는 체감이 아니라 생산성 차이로 이어진다.


정리

  • No qualifying bean 오류는 단순한 설정 문제가 아니다
    의존성 설계 문제의 신호
  • @Autowired 필드 주입
    → 의존성을 숨기고 문제를 늦게 드러낸다
  • 생성자 주입
    → 의존성을 명확히 하고 문제를 빠르게 드러낸다
  • @RequiredArgsConstructor
    → 생성자 주입을 가장 간결하고 안전하게 사용할 수 있는 방법
반응형