오늘은 약 한 달 전쯤 만났던 NoUniqueBeanDefinitionException 해결방법에 대해 드디어 정리해본다.
하나의 interface를 implement하는 class가 2개가 있었고 각각의 class에서 구현부 작성한 뒤 실행시키자 아래와 같은 에러가 발생했다.
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'companyService' defined in file [C:\projects\....\CompanyService.class]: Unsatisfied dependency expressed through constructor parameter 3; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.,,,.AbcService' available: expected single matching bean but found 2: firstImpl,secondImpl
2023-02-08 16:18:29 [restartedMain] [INFO ] o.a.catalina.core.StandardService - Stopping service [Tomcat]
Description:
Parameter 3 of constructor in com.,,,.CompanyService required a single bean, but 2 were found:
- firstImpl: defined in file [C:\projects\Abc\build\classes\java\main\com\...\service\FirstImpl.class]
- secondImpl: defined in file [C:\projects\Abc\build\classes\java\main\com\...\service\SecondImpl.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
NoUniqueBeanDefinitionException로 확인되며 원인과 해결 방법도 친절히 잘 설명되어 있다.
간단히 정리하면 의존성을 주입할 객체가 2개(firstImpl, secondImpl)가 존재한다는 것이다.
가장 하단에 3가지 해결 방법에 대해 알려준다.
- @Primary annotation 활용
- 해당 타입의 Bean 모두 주입받기
- @Qualifier annotation 활용
안타깝지만 나는 이 중 3번의 방법을 이미 사용하고 있었다.
코드는 아래와 같다.
찬찬히 다시 살펴본다.
public interface AbcService {
void abcabc();
...
}
@Slf4j
@Service("FirstImpl")
@RequiredArgsConstructor
public class FirstImpl implements AbcService {
...
}
@Slf4j
@Service("SecondImpl")
@RequiredArgsConstructor
public class SecondImpl implements AbcService {
...
}
위와 같이 FirstImpl이라는 이름의 객체와 SecondImpl라는 이름의 객체를 스프링 컨테이너가 생성한다.
이 두 객체는 모두 AbcService 인터페이스를 구현한 객체이며 AbcService 타입의 객체를 의존성 주입하고자 하면 AbcService를 구현한 2개의 객체, 즉 FirstImpl과 SecondImpl가 존재한다.
@Slf4j
@Service
@RequiredArgsConstructor
public class CompanyService {
private final MemberService memberService;
@Qualifier("FirstImpl")
private final AbcService abcService;
...
}
여기서 2가지 객체 중 abcService 변수에 어떤 객체를 주입해야할지 스프링 컨테이너는 정확히 알 수 없다. 이 때 NoUniqueBeanDefinitionException 가 발생한다.
하지만 나는 앞서 말했듯, 그리고 위 코드를 보면 알 수 있듯, 이 에러를 해결하기 위한 3가지 방법 중 3번인 @Qualifier 어노테이션을 이미 사용하고 있었음에도 동일한 에러가 발생했다.
구글링을 해도 위의 3가지 방법에 대해서만 반복해서 검색이 됐고, 그러던 중 이 글을 보게 되었고 참고해서 드디어 문제를 해결했다!
한 줄로 정리하자면 lombok이 제공하는 @RequiredArgsConstructor 는 어노테이션까지 함께 포함해 생성자를 만들지 않는다.
즉, lombok의 @RequiredArgsConstructor 는 @Qualifier 를 읽어주지 못한다는 것이다.
참고한 글에서는 lombok.config 파일을 따로 만들어 lombok.copyableAnnotations += org.springframework.beans.factory.annotation.Qualifier 을 추가하며 lombok AnnotationProcessor가 복사할 대상(필드에 선언된 annotation), 여기서는 @Qualifier을 명시적으로 선언해주었다.
나는 따로 config 파일을 생성하지는 않았고, @RequiredArgsConstructor 를 사용하지 않고 내부에서 생성자를 직접 만들어주고 @Qualifier annotation을 붙여서 사용했다.
구현한 코드는 아래와 같다.
@Slf4j
@Service
//@RequiredArgsConstructor // 주석 또는 삭제 처리
public class CompanyService {
private final MemberService memberService;
private final AbcService abcService;
// 생성자 직접 생성하고 @Qualifier annotation 붙여서 사용
public CompanyService(MemberService memberService, @Qualifier("FirstImpl") AbcService abcService) {
this.memberService = memberService;
this.abcService = abcService;
}
...
}
@Slf4j
@Service
@RequiredArgsConstructor
@Qualifier("FirstImpl")
public class FirstImpl implements AbcService {
...
}
@Slf4j
@Service
@RequiredArgsConstructor
@Qualifier("SecondImpl")
public class SecondImpl implements AbcService {
...
}
이상이다!
'Spring & Spring Boot' 카테고리의 다른 글
[Error/Exception] Spring Boot 3에서 Java EE에서 Jakarta EE로 전환 관련 에러 (0) | 2023.09.28 |
---|---|
[Spring Boot] application 실행 후 Process finished with exit code 0 (0) | 2023.06.18 |
[Error/Exception] PessimisticLockException과 데드락(Deadlock) (0) | 2023.06.15 |
[Error/Exception] Error parsing HTTP request header 에러 해결 (3) | 2023.03.18 |
[Spring] @Transactional의 noRollbackFor 속성 (0) | 2022.12.22 |