Field testService in com.example.TestController required a single bean, but 3 were found:
	- testServiceImpl1: defined in file [build/classes/java/main/com/example/TestServiceImpl1.class]
	- testServiceImpl2: defined in file [build/classes/java/main/com/example/TestServiceImpl2.class]
	- testServiceImpl3: defined in file [build/classes/java/main/com/example/TestServiceImpl3.class]
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

@Primary, @Qualifier

이런 문제에서 생기는 스프링 에러다.

빈이 여러 개인 경우 생기는 문제인데

밑이 보면 해결 방법으로 3가지를 제시해 준다.

  1. Consider marking one of the beans as @Primary
  2. updating the consumer to accept multiple beans
  3. using @Qualifier to identify the bean that should be consumed

예제 코드로 확인해보자

TestController

@RequiredArgsConstructor
@RestController
public class TestController {

    private final TestService testService;

    @GetMapping("")
    public String test() {
        return String.valueOf(testService.test());
    }

}

TestService

public interface TestService {
    int test();
}

@Service
public class TestServiceImpl1 implements TestService {

    @Override
    public int test() {
        return 1;
    }

}

@Service
public class TestServiceImpl2 implements TestService {

    @Override
    public int test() {
        return 2;
    }

}

@Service
public class TetServiceImpl3 implements TestService {

    @Override
    public int test() {
        return 3;
    }

}

이렇게 돼있을 때 실행 시 게시글 상단의 에러를 만날 수 있다.

@Primary

그럼 첫 번째 방법을 사용해 본다.

@Primary
@Service
public class TestServiceImpl1 implements TestService {

    @Override
    public int test() {
        return 1;
    }

}

내가 원하는 클래스에 @Primary를 붙여주고 확인하면 그 클래스로 실행이 된다.

나는 TestServiceImpl1에 붙여줬으니 1이 나온다.

List

다음으로 두 번째 방법은 List로 만들어 사용하는 것이다.

@RequiredArgsConstructor
@RestController
public class TestController {

    private final List<TestService> testServiceList;

    @GetMapping("")
    public String test() {
        return String.valueOf(testServiceList.get(1).test());
    }

}

이렇게 하게 되면 3개의 클래스 모두 사용 가능한데 여기서 get()으로 인덱스 값을 넣어서 가져오면 된다.

리스트에 들어가는 순서는 클래스 이름순이다.

따로 명시하지 않아도 TetServiceImpl1 -> tetServiceImpl1, TetServiceImpl2 -> tetServiceImpl2 이렇게 된다.

@Qualifier

마지막으로 세 번째 방법으로는 @Qualifier를 사용하는 것이다.

세 번째 방법에는 여러 가지의 방법이 있다.

@RestController
public class TestController {

    @Autowired
    @Qualifier("testServiceImpl3")
    private TestService testService;

    @GetMapping("")
    public String test() {
        return String.valueOf(testService.test());
    }

}

위의 코드처럼 @Autowired를 사용할 경우 @Qualifier("") 명시해 주면 된다.

생성자를 통한 주입의 경우에는 밑에처럼 사용하면 된다.

@RestController
public class TestController {

    private final TestService testService;

    public TestController(@Qualifier("testServiceImpl3") TestService testService) {
        this.testService = testService;
    }

    @GetMapping("")
    public String test() {
        return String.valueOf(testService.test());
    }

}

마지막으로 Lombok의 @RequiredArgsConstructor와 같이 사용할 경우에는 문제가 있지만 이 방법으로 사용하기 위해서는 프로젝트의 루트 폴더에 밑에 lombok.config 파일을 만들어주고 아래의 내용을 적어주면 된다.

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

작성이 끝났다면 RequiredArgsConstructor와 함께 사용해 보자

@RequiredArgsConstructor
@RestController
public class TestController {

    @Qualifier("testServiceImpl3")
    private final TestService testService;

    @GetMapping("")
    public String test() {
        return String.valueOf(testService.test());
    }

}

이렇게 해주면 고민이 해결된다.

마지막으로 기본으로 설정되는 이름 말고 본인이 원하는 이름을 사용하고 싶은 경우에는 아래와 같이 사용하자

@Qualifier("test1")
@Service
public class TestServiceImpl1 implements TestService {

    @Override
    public int test() {
        return 1;
    }

}

@Qualifier("test2")
@Service
public class TestServiceImpl2 implements TestService {

    @Override
    public int test() {
        return 2;
    }

}

마지막 @RequiredArgsConstructor & @Qualifier의 참조 링크

SPRING + LOMBOK + @QUALIFIER OR INJECTION JUST BECAME A BIT EASIER (PART 2 OF 2)

- 끗 -

hahahaha