본문 바로가기
spring & boot/Spring & Spring Boot

[Spring Boot] @Qualifier 무시 오류(Lombok @RequiredArgsConstructor)

by lucas_owner 2025. 2. 5.

 

@Qualifier 무시 오류(Lombok @RequiredArgsConstructor)

라이브러리 개발도중 발생한 문제이다. 

우선 Bean 과 의존성 주입에 대해서 간략하게 살펴보도록 하자.

 

Spring IoC Container 는 Bean 을 생성,관리 하여 의존성 주입을 대신 해준다.

다만 Bean 으로 등록하게 될 타입이 여러개가 존재한다면 개발자가 어떤 Type 을 사용할 것인지 명시해 주어야 한다.

 

// Interface
public interface ServiceTarget{}

// Impl 1
@Service
public class TargetImpl implements ServiceTarget {}

// Impl 2
@Service
public class TargetImpl2 implements ServiceTarget {}


// use
@RestController
public class Controller {
	
    @Autowired
    private final ServiceTarget service;
}

위의 예시 코드는 어떤 Bean 을 명시해 주지 않은 예제 코드이다. 

이경우 IDE 에서부터 Warn 을 하게 된다. interface 의 구현체가 2개인데 어떤것을 사용하여 Bean 생성, 주입을 할지 IoC Container 에서는 모르기 때문이다. 이럴경우 Bean 을 구분하는 방법은 아래와 같다.


  1. Field 명 Mathcing 
  2. @Primary
  3. @Qualifier

 

1. Field 명 매칭

// use
@RestController
public class Controller {
	
    @Autowired
    private final ServiceTarget targetImpl;
}

 

 

Spring 의 경우 동일 타입의 Bean 이 여러개라면, 이름으로 한번더 조회하게 된다.

이 경우 interface 의 어떤 구현체를 사용할것인지 해당 구현체의 이름으로 적용하여 사용 할수있다.

 

2. @Primary

// Interface
public interface ServiceTarget{}

// Impl 1
@Service
@Primary
public class TargetImpl implements ServiceTarget {}

// Impl 2
@Service
public class TargetImpl2 implements ServiceTarget {}


// use
@RestController
public class Controller {
	
    @Autowired
    private final ServiceTarget service;
}

해당 어노테이션은 동일 타입 Bean 이 여러개일때 우선순위를 지정해준다.

동일 타입 Bean 중 가장 우선권을 가지며, 별다른 설정이 되어있지 않을때 Bean 으로 등록이 된다.

 

3. @Qualifier

// Interface
public interface ServiceTarget{}

// Impl 1
@Service
@Primary
public class TargetImpl implements ServiceTarget {}

// Impl 2
@Service
public class TargetImpl2 implements ServiceTarget {}


// use
@RestController
public class Controller {
	
    @Qualifier("targetImpl2")
    @Autowired
    private final ServiceTarget service;
}

해당 어노테이션은 여러개의 타입중 어떤것을 구분하여 사용할것인지 지정해주는 역할을 한다.

 

하지만 위의 코드 처럼 @Primary 와 @Qualifier 가 동시에 존재하는 경우

@Qualifier 의 타입이 우선권을 가지게 된다.

 

Spring 에서 @Primary, @Qualifier 중 Bean 의 우선권을 지정하는 코드는 

org.springframework.beans.factory.support.DefaultListableBeanFactory.determineAutowireCandidate() 내에 존재한다.


문제 상황

대략적으로 Bean 의 주입과 우선권과 같은 기본 지식을 알아봤으니 실질적 문제상황으로 가보도록 하겠다. 

public interface SampleRepository {
    void save(String msg);
    String get(String msg);
}

위와 같은 Interface 가 존재하고, 구현체는 2개가 존재하며, @Primary 가 붙은 Class, 붙지않은 Class 가 존재한다.

(@Qualifier 의 샘플 코드와 구조가 동일하다.)

 

First Impl
Second Impl

 

Use Class

 

 

@Qualifier 어노테이션을 사용 하였으니 당연히 Log 에는 'SECOND ~' 관련 해서 남아야 하는데 'FIRST ~' 관련 log 만 남았다.

DipController.class 를 열어보면 아래와 같이 @Qualifier 가 빠져있는걸 확인할 수 있다.

 

 

이러한 문제는 Lombok 과의 충돌로 발생하였으며

https://github.com/projectlombok/lombok/issues/745 의 티켓에서 확인 할 수 있었다.

롬복의 @RequiredArgsConstructor 은 어노테이션을 포함하여 Constructor 를 만들지 않기 때문에 발생한 문제였다. 

 

1. 생성자 주입 방식 해결

Lombok 을 사용하지 않고 전통적인 방식으로 사용한다면 당연히 Qualifier 의 타입이 사용된다.

@RestController
@Slf4j
@RequestMapping("/api/v1/dip")
public class DipController {

    @Qualifier("sampleRepositoryImpl_second")
    private final SampleRepository sampleRepository;

    public DipController(@Qualifier("sampleRepositoryImpl_second") SampleRepository sampleRepository) {
        this.sampleRepository = sampleRepository;
    }

 

 

2. Lombok 과 함께 사용하는 방법

 

/src/main/java/lombok.config 파일을 추가하여 아래와 같은 코드를 추가해주면 된다.

lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier

 

 

해당 코드는 롬복 어노테이션 프로세서가 사용할 대상(필드) 의 어노테이션을 명시적으로 선언하는것이다.

 

이걸로 Lombok 의 @RequiredArgsConstructor 와 @Qualifier 를 같이 사용하여도 문제가 발생하지 않는다.

반응형

댓글