본문 바로가기
개발/SpringBoot

Consider defining a bean of type 'Class' in your configuration. 에러 해결 [Spring Boot]

by Mingvel 2022. 3. 24.

개인 프로젝트를 진행하던 중, 상상치도 못한 부분에서 에러가 발생했다.

 

SpringBoot Application을 실행(Run) 하는 과정에서 발생한 에러는 다음과 같다.

 

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in 
 com.ming.abstractservice.domain.car.service.CarService required a bean of type 
  'com.ming.abstractservice.domain.car.internal.CarClient' that could not be found.


Action:

Consider defining a bean of type 'com.ming.abstractservice.domain.car.internal.CarClient' in your configuration.

 

에러의 내용을 살펴보면 'CarClient' 라는 인터페이스를 주입받아서 사용하는 'CarService'에서 

 

'CarClient'라는 Bean을 찾을 수 없다는 내용으로 보인다.

 

에러의 원인인 'CarService'와 'CarClient' 클래스의 코드는 다음과 같다.

 

CarService

package com.ming.abstractservice.domain.car.service;

import org.springframework.stereotype.Service;
//import 생략

@RequiredArgsConstructor //CarClient 생성자 주입
@Service
public class CarService {

    private final CarClient client;

    public CarCreateResponse create(CarCreateRequest request) {

        Car car = client.createNewCar(request);

        return CarCreateResponse.builder()
                .carId(car.getId())
                .carName(car.getName())
                .build();
    }
}

 

 

CarClient

package com.ming.abstractservice.domain.car.internal;

import org.springframework.stereotype.Component;
//import 생략...

@Component
public interface CarClient extends V3AbstractInternal { //확장

    default Car createNewCar(CarCreateRequest request) {

        return Car.builder()
                .id(IdGenerator.getNumberId())
                .type(request.getType())
                .color(request.getColor())
                .name(request.getName())
                .price(request.getPrice())
                .build();
    }

    default Car v3CreateNewCar(V3CarCreateRequest request) {

        return Car.builder()
                .id(IdGenerator.getNumberId())
                .type(request.getType())
                .color(request.getColor())
                .name(request.getName())
                .price(request.getPrice())
                .build();
    }

    @Override
    default V3AbstractEntity createEntity(V3CreateRequestDto requestDto) {

        return v3CreateNewCar((V3CarCreateRequest) requestDto);
    }
}

 

의심이 되는 부분은 세가지였다.


  1. SpringBootApplication과 해당 Bean(Component)과의 패키지 경로가 다른가
  2. ComponentScan을 명시적으로 작성해줘야 하는 상황인가
  3. Bean 등록과 관련된 Annotation의 누락사항은 없는가

위 세 가지 의심 사항들을 살펴보기 전에 SpringBootApplication 클래스도 함께 보도록 하자

 

SpinrgBootApplication

package com.ming.abstractservice;

//import 생략

@SpringBootApplication
public class AbstractServiceApplication {

    public static void main(String[] args) {

        SpringApplication.run(AbstractServiceApplication.class, args);
    }
}

 

세가지 의심사항을 차근차근 살펴보자

 

 

SpringBootApplication과 해당 Bean(Component)과의 패키지 경로가 다른가

 

 

Spring은 기본적으로 @SpringBootApplication 어노테이션이 붙은 클래스가 위치한 패키지를 Base Package로

해당 Package 하위 Package에 선언된 Bean을 Bean으로 등록하고 사용한다.

 

위 예시에서 등장한 'AbstractServiceApplication'의 Package 경로와 'CarClient'의 Package 경로를 보자 

 

Class명 AbstractServiceApplication CarClient
Package 경로 com.ming.abstractservice com.ming.abstractservice.domain.car.internal

 

CarClient는 base package인 'com.ming.abstractservice' 하위에 위치하는 것으로 확인되었다.

따라서 의심사항 1번은 문제없다는 결론을 얻을 수 있다.

 

 

@ComponentScan 어노테이션을 명시적으로 작성해줘야 하는 상황인가

 

 

 

Spring을 사용하다 보면 @ComponentScan 어노테이션을 명시적으로 작성해줘야 하는 상황이 있다.

 

대표적인 상황으로는 실제 SpringBootApplication을 실행하는 Package(base package) 경로와

 

사용하고자 하는 Bean의 Package 경로가 다른 경우이다.

 

그런 경우엔 @ComponentScan 어노테이션을 직접 명시하여, 해당 Bean을 사용할 수 있다.

 

하지만 위 예시에선 두 항목의 package가 일치하고, @SpringBootApplication 어노테이션 내부에 @CompoentScan 어노테이션을 포함하고 있어,

의심사항 2번 또한 문제가 없다는 결론을 얻을 수 있다.

 

 

 

Bean 등록과 관련된 Annotation의 누락사항은 없는가

 

 

위 예시의 'CarClient'는 @Component 어노테이션을 사용하였고,

 

'CarService'는 @Service 어노테이션 사용 및 CarClient를 생성자 주입하였다.

 

따라서 의심사항 3번 또한 문제가 없다는 결론을 얻을 수 있다.

 

 

 

 

그럼 도대체 무엇이 문제이고, 어떻게 해결해야 하는가?

 

 

해결 방법은 삽질한 시간이 허무할 정도로 간단했다

 

바로

 

Interface로 선언 한 'CarClient'를 Class로 변경하는 것이다.

 

JPA Repository(interface)를 Repository(Bean)으로 등록하여 당연스럽게 사용하던 나에겐 신선한 충격이었고,

 

즉시 'CarClient' interface를 class로 변경하였다.

 

변경 후 'CarClient' 클래스는 다음과 같다.

package com.ming.abstractservice.domain.car.internal;

import org.springframework.stereotype.Component;
//import 생략

@Component
public class CarClient implements V3AbstractInternal { //합성

    public Car createNewCar(CarCreateRequest request) {

        return Car.builder()
                .id(IdGenerator.getNumberId())
                .type(request.getType())
                .color(request.getColor())
                .name(request.getName())
                .price(request.getPrice())
                .build();
    }

    public Car v3CreateNewCar(V3CarCreateRequest request) {

        return Car.builder()
                .id(IdGenerator.getNumberId())
                .type(request.getType())
                .color(request.getColor())
                .name(request.getName())
                .price(request.getPrice())
                .build();
    }

    @Override
    public V3AbstractEntity createEntity(V3CreateRequestDto requestDto) {

        return v3CreateNewCar((V3CarCreateRequest) requestDto);
    }
}

 

코드를 수정하면서 'CarClient'와 'V3AbstractInternal' 과의 관계를 

확장 -> 합성 의 관계로 변경하는 리펙토링 효과도 얻었다.

 

기본적인 실수하지 않는 개발자가 되기 위해 오늘도 한걸음 더!

반응형

댓글