레이블이 순환 의존성인 게시물을 표시합니다. 모든 게시물 표시
레이블이 순환 의존성인 게시물을 표시합니다. 모든 게시물 표시

일요일

BeanCurrentlyInCreationException 해결 — Spring Boot 순환 의존성 에러 완전 정복

🔍 검색 키워드: BeanCurrentlyInCreationException 해결, Spring Boot 순환 의존성, circular dependency 에러, Spring Boot 빈 생성 실패, allow-circular-references, Spring Boot 시작 실패

Spring Boot 프로젝트를 띄우다가 서버가 뜨지도 않고 이런 에러를 만난 적 있을 것이다.

The dependencies of some of the beans in the application context form a cycle:
userService -> orderService -> userService

혹은 더 직접적으로:

org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'userService':
Requested bean is currently in creation: Is there an unresolvable circular reference?

서버 자체가 뜨지 않으니 당황스럽다. 이 글에서는 원인부터 실무에서 자주 쓰는 해결책까지 정리한다.

순환 의존성이란 뭔가

ServiceA가 ServiceB를 주입받고, ServiceB가 다시 ServiceA를 주입받는 상황이다. Spring이 A를 만들려면 B가 필요하고, B를 만들려면 A가 필요하다. 닭이 먼저냐 달걀이 먼저냐 — Spring은 이 상황에서 예외를 던진다.

@Service
public class UserService {
    private final OrderService orderService;

    public UserService(OrderService orderService) { // OrderService 주입
        this.orderService = orderService;
    }
}

@Service
public class OrderService {
    private final UserService userService;

    public OrderService(UserService userService) { // UserService 주입
        this.userService = userService;
    }
}

위처럼 생성자 주입(Constructor Injection)으로 서로를 참조하면 Spring Boot 2.6 이후부터는 기본적으로 예외가 발생한다.

왜 Spring Boot 2.6부터 더 자주 보이나

Spring Boot 2.6에서 기본 순환 의존성 감지가 강화됐다. 이전 버전에서는 필드 주입(@Autowired)의 경우 특별한 설정 없이도 넘어가는 경우가 있었는데, 2.6부터는 훨씬 엄격하게 잡아낸다. 기존에 잘 돌아가던 프로젝트를 Spring Boot 버전 올리다가 갑자기 이 에러를 만나는 이유다.

에러 발생 상황 체크리스트

상황가능성
Service 간 서로 참조★★★★★
Service → Repository → Service 체인★★★★☆
@Configuration 클래스 간 참조★★★☆☆
Spring Boot 버전 업그레이드 직후★★★★☆
이벤트 리스너와 서비스 간 참조★★★☆☆

해결 방법 1 — @Lazy 어노테이션 (빠른 임시 해결)

둘 중 하나의 의존성에 @Lazy를 붙인다. Spring이 실제로 해당 빈이 필요한 시점까지 초기화를 미룬다.

@Service
public class UserService {
    private final OrderService orderService;

    public UserService(@Lazy OrderService orderService) { // @Lazy 추가
        this.orderService = orderService;
    }
}

당장 급하게 해결해야 할 때 쓰는 방법이다. 하지만 근본적인 구조 문제를 가리는 것이라 장기적으로는 좋지 않다.

해결 방법 2 — 세터/필드 주입으로 전환

생성자 주입 대신 세터 주입(Setter Injection)으로 바꾸면 Spring이 빈을 먼저 만든 후 의존성을 주입하기 때문에 순환 참조 문제가 해소된다.

@Service
public class OrderService {
    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
}

해결 방법 3 — 구조 리팩토링 (가장 올바른 해결)

순환 의존성이 생겼다는 건 클래스의 책임이 잘못 분리됐다는 신호다. 보통 아래 패턴 중 하나로 해결된다.

공통 로직을 별도 서비스로 분리:

// UserService와 OrderService 모두 참조하던 공통 로직
@Service
public class UserOrderBridgeService {
    private final UserRepository userRepository;
    private final OrderRepository orderRepository;

    public void processUserOrder(Long userId, Long orderId) {
        // 공통 로직 처리
    }
}

@Service
public class UserService {
    private final UserRepository userRepository;
    private final UserOrderBridgeService bridgeService; // 공통 서비스만 참조
}

@Service
public class OrderService {
    private final OrderRepository orderRepository;
    private final UserOrderBridgeService bridgeService;
}

이벤트 기반으로 분리:

@Service
public class UserService {
    private final ApplicationEventPublisher eventPublisher;

    public void deleteUser(Long userId) {
        eventPublisher.publishEvent(new UserDeletedEvent(userId));
    }
}

@Component
public class OrderEventListener {
    private final OrderService orderService;

    @EventListener
    public void handleUserDeleted(UserDeletedEvent event) {
        orderService.cancelOrdersByUserId(event.getUserId());
    }
}

해결 방법 4 — application.properties 설정 (비추천)

spring.main.allow-circular-references=true

이 설정은 에러만 안 보이게 하는 것이다. 실제로는 Spring Boot가 내부적으로 의존성 순서를 휴리스틱하게 결정하게 되어, 예측하기 어려운 초기화 순서 문제가 생길 수 있다. 근본 원인을 분석할 시간이 없는 긴급 상황에서 임시로만 쓰자.

정리

  • BeanCurrentlyInCreationException은 두 빈이 서로 의존하는 구조에서 발생한다
  • Spring Boot 2.6+에서 기본적으로 더 엄격하게 감지한다
  • 빠른 해결은 @Lazy, 올바른 해결은 공통 로직 분리 또는 이벤트 기반 구조 변경
  • allow-circular-references=true는 임시방편일 뿐, 운영 환경에서는 근본 해결 필요

순환 의존성 에러는 코드 냄새(Code Smell)다. 에러를 꺼주는 것보다 왜 두 서비스가 서로를 참조해야 하는지를 먼저 물어보는 게 맞다.