🤔 순환 참조(Circular Dependency)란?

Spring을 공부하다 보면 한 번쯤 이런 에러를 만나게 된다.

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

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  AService defined in file [/Users/byeonguk/Desktop/egov-vscode2026/circular-reference-demo/target/classes/com/example/circular/bad/AService.class]
↑     ↓
|  BService defined in file [/Users/byeonguk/Desktop/egov-vscode2026/circular-reference-demo/target/classes/com/example/circular/bad/BService.class]
└─────┘

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

또는 Spring Boot 2.6 이상 환경에서는 애플리케이션 실행 중 다음과 비슷한 메시지를 볼 수 있다.

The dependencies of some of the beans in the application context form a cycle

처음 이 에러를 보면 단순히 “Bean 주입이 꼬였나?” 정도로 생각할 수 있다. 하지만 Spring Bean 순환 참조는 단순한 설정 오류가 아니다. 오히려 이 에러는 애플리케이션 내부의 객체들이 서로 너무 강하게 얽혀 있고, 책임 분리가 제대로 되어 있지 않다는 신호에 가깝다.

이번 글에서는 Spring Bean 순환 참조가 무엇인지, 왜 발생하는지, Spring 컨테이너 내부에서는 어떤 일이 일어나는지, 그리고 실무에서는 어떻게 해결해야 하는지 정리해보려고 한다.

 

🫛 Spring Bean 이해하기

순환 참조를 이해하려면 먼저 Spring Bean이 무엇인지부터 정리해야 한다. 알디시피 Spring에서는 개발자가 직접 객체를 생성하고 관리하기보다, Spring 컨테이너가 객체의 생성과 의존성 주입을 대신 관리한다.

예를 들어 다음과 같은 클래스가 있다고 하자.

@Service
public class OrderService {
    private final PaymentService paymentService;

    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

OrderService는 직접 new PaymentService()를 호출하지 않는다. 대신 생성자를 통해 PaymentService를 주입받는다. 이때 OrderServicePaymentService 같은 객체를 Spring 컨테이너가 관리하면 이를 Bean이라고 부른다.

Spring 컨테이너는 애플리케이션이 시작될 때 @Component, @Service, @Repository, @Controller, @Configuration 같은 어노테이션이 붙은 클래스를 스캔한다. 그리고 이 클래스들을 기반으로 Bean을 생성하고, 필요한 의존성을 자동으로 연결해준다.

이것이 바로 Spring의 핵심 개념인 IoC(Inversion of Control, 제어의 역전)DI(Dependency Injection, 의존성 주입)이다. 개발자가 객체 생성과 연결을 직접 제어하는 것이 아니라, Spring 컨테이너가 객체의 생명주기와 의존 관계를 관리하는 것이다.

 

🔄 순환 참조란 무엇인가?

순환 참조는 이름 그대로 의존성의 흐름이 원처럼 순환하는 구조를 말한다.

가장 단순한 예시는 다음과 같다.

A → B → A

A는 B가 필요하고, B는 다시 A가 필요한 구조다.

 

코드로 보면 다음과 같다.

@Service
public class AService {
    private final BService bService;

    public AService(BService bService) {
        this.bService = bService;
    }
}
@Service
public class BService {
    private final AService aService;

    public BService(AService aService) {
        this.aService = aService;
    }
}

AService를 만들려면 BService가 필요하다. 그런데 BService를 만들려면 다시 AService가 필요하다.

 

Spring 컨테이너 입장에서는 이런 상황이 된다.

  1. "AService를 만들려고 했더니 BService가 필요하네?"
  2. "그럼 BService를 먼저 만들자."
  3. "그런데 BService를 만들려고 했더니 AService가 필요하네?"
  4. "다시 AService를 만들어야 하나?"
  5. "그런데 AService를 만들려면 BService가 필요한데?"

결국 누구를 먼저 만들어야 하는지 결정할 수 없는 상황, 이것이 바로 Spring Bean 순환 참조다. “닭이 먼저냐, 달걀이 먼저냐” 문제와 아주 비슷하다. A를 만들려면 B가 필요하고, B를 만들려면 A가 필요하다. 둘 중 하나가 먼저 완성되어야 다른 하나를 만들 수 있는데, 둘 다 서로가 먼저 필요하다고 말하는 상황이다.

 

💣 생성자 주입에서 순환 참조가 더 치명적인 이유

Spring에서는 의존성을 주입하는 방식이 여러 가지 있다.

대표적으로 생성자 주입(Constructor Injection), 세터 주입(Setter Injection), 필드 주입(Field Injection)가 있다. 이 중 현대 Spring 개발에서는 생성자 주입이 가장 권장된다. 생성자 주입은 필수 의존성을 객체 생성 시점에 반드시 전달받도록 강제할 수 있다. 또한 필드를 final로 선언할 수 있어 객체의 불변성을 지키기 좋고, 테스트 코드 작성도 쉽다.

 

하지만 순환 참조 상황에서는 생성자 주입이 가장 빠르게 문제를 드러낸다.

@Service
public class OrderService {
    private final UserService userService;

    public OrderService(UserService userService) {
        this.userService = userService;
    }
}
@Service
public class UserService {
    private final OrderService orderService;

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

OrderService 객체를 생성하려면 생성자 인자로 UserService가 필요하다. 그런데 UserService 객체를 생성하려면 생성자 인자로 OrderService가 필요하다.

생성자 주입에서는 객체가 완성되기 전에 필요한 의존성이 모두 준비되어 있어야 한다. 따라서 서로가 서로의 생성자 인자로 필요한 상황에서는 객체를 하나도 완성할 수 없다.

그래서 생성자 주입 기반의 순환 참조는 Spring 컨테이너가 해결하기 어렵고, 애플리케이션 시작 단계에서 바로 예외가 발생한다. 불편해 보일 수 있지만, 사실 이건 좋은 신호이기도 하다. 생성자 주입은 잘못된 의존 관계를 애플리케이션 실행 초기에 명확히 드러내기 때문이다.

 

💉 세터 주입과 필드 주입

과거 Spring Boot 2.5 이하 환경에서는 세터 주입이나 필드 주입을 사용하면 순환 참조가 동작하는 경우가 있었다.

예를 들어 다음과 같은 코드다.

@Service
public class AService {
    @Autowired
    private BService bService;
}
@Service
public class BService {
    @Autowired
    private AService aService;
}

생성자 주입과 달리 필드 주입은 객체를 먼저 만든 뒤 나중에 필드를 채운다.

 

즉, Spring 컨테이너는 다음과 같은 방식으로 처리할 수 있다.

  1. AService 객체를 일단 만든다.
  2. 아직 bService 필드는 비어 있다.
  3. BService 객체를 만든다.
  4. BService에 AService의 미완성 참조를 넣는다.
  5. BService 생성이 끝난다.
  6. 다시 AService로 돌아와 bService 필드를 채운다.

이런 방식이 가능했던 이유는 Spring 내부에 3단계 캐시 메커니즘이 있었기 때문이다.

 

🏰 Spring의 3단계 캐시 메커니즘

Spring은 싱글톤 Bean을 생성하고 관리하기 위해 내부적으로 여러 캐시를 사용한다. 순환 참조와 관련해서 자주 언급되는 것이 바로 3단계 캐시다.

캐시 단계내부 변수명저장 대상의미
1단계 캐시singletonObjects완전히 생성된 Bean인스턴스화, 의존성 주입, 초기화까지 끝난 정상 Bean
2단계 캐시earlySingletonObjects조기 참조 Bean아직 완전히 초기화되지는 않았지만 참조 가능한 미완성 Bean
3단계 캐시singletonFactoriesObjectFactory필요할 때 조기 참조 객체를 만들어 반환할 수 있는 팩토리

이 구조를 아주 단순하게 설명하면 다음과 같다.

Spring은 A Bean을 만들기 시작하면, 아직 완전히 완성되지 않았더라도 “A라는 객체의 참조를 나중에 꺼낼 수 있는 통로”를 3단계 캐시에 등록해둔다.

이후 B Bean을 만들다가 A Bean이 필요해지면, Spring은 완성된 A Bean이 없더라도 3단계 캐시에서 A의 조기 참조를 꺼내 B에 주입한다.

그 후 B가 완성되면 다시 A로 돌아와 B를 주입하고 A도 완성한다.

즉, Spring은 Java 객체가 참조 타입이라는 특성을 이용해서 미완성 객체의 참조를 임시로 넘겨주는 방식으로 일부 순환 참조를 해결해왔다. 하지만 이 방식은 어디까지나 제한적인 우회 방식이다.

 

❓ AOP가 끼어들면 왜 더 복잡해질까?

Spring 애플리케이션에서는 @Transactional, @Async 같은 기능을 자주 사용한다. 이런 기능은 Spring AOP를 통해 동작한다. Spring은 원본 객체를 그대로 사용하는 것이 아니라, 원본 객체를 감싼 프록시 객체를 만들어 사용한다.

예를 들어 OrderService@Transactional이 붙어 있다면, Spring 컨테이너가 관리하는 실제 Bean은 순수한 OrderService 객체가 아니라 트랜잭션 기능이 적용된 프록시 객체일 수 있다.

문제는 순환 참조 상황에서 조기 참조로 노출된 객체와 최종적으로 컨테이너에 등록되는 객체가 달라질 수 있다는 점이다.

예를 들어 B Bean이 A Bean의 조기 참조를 주입받았는데, 그 시점의 A는 아직 프록시가 적용되기 전의 원본 객체라고 해보자. 이후 A의 초기화가 끝나면서 Spring이 A를 프록시 객체로 감싼다. 그러면 컨테이너가 관리하는 공식 A Bean은 프록시 객체인데, B 내부에 들어 있는 A는 원본 객체일 수 있다.

이렇게 되면 트랜잭션, 비동기 처리, 보안 같은 AOP 기능이 기대한 대로 동작하지 않을 수 있다. Spring이 내부적으로 이런 문제를 줄이기 위한 복잡한 방어 로직을 갖고 있지만, 모든 상황을 완벽히 안전하게 처리하기는 어렵다. 그래서 순환 참조는 단순히 Spring이 알아서 해결해주면 되는 문제로 보면 안 된다.

 

🚫 Spring Boot 2.6부터 순환 참조가 기본적으로 금지된 이유

Spring Boot 2.6부터는 순환 참조가 기본적으로 금지되었다. 이전에는 Spring이 일부 순환 참조를 내부 캐시를 통해 해결해주었지만, 이제는 애플리케이션 시작 시점에 순환 참조를 발견하면 기본적으로 실패하도록 바뀌었다. 이 정책 변화는 매우 중요하다.

Spring이 순환 참조를 막는 이유는 개발자를 불편하게 만들기 위해서가 아니라 오히려 애플리케이션 구조의 문제를 더 빨리 발견하게 하기 위해서다. 순환 참조는 대부분 다음과 같은 설계 문제를 의미한다.

  • 두 객체의 책임이 명확히 분리되어 있지 않다.
  • 서로 다른 도메인이 너무 강하게 결합되어 있다.
  • 한 클래스가 너무 많은 일을 하고 있다.
  • 의존성 방향이 정리되어 있지 않다.

 

즉, 순환 참조는 단순 에러라기보다 코드 스멜(Code Smell)에 가깝다.

Spring Boot 2.6의 기본 차단 정책은 이런 구조적 문제를 애플리케이션 실행 초기에 드러내는 Fail-fast 전략이라고 볼 수 있다. Fail-fast 전략이란 문제가 있다면 가능한 한 빨리 실패하게 해서, 더 큰 장애로 번지기 전에 개발자가 문제를 수정하도록 하는 방식을 말한다.

 

👀 allow-circular-references=true는 해결책일까?

릴리즈 노트를 보면 알 수 있듯이 Spring Boot 2.6 이상에서도 설정을 통해 순환 참조를 다시 허용할 수 있다.

spring:
  main:
    allow-circular-references: true

또는 application.properties에서는 다음처럼 설정할 수 있다.

spring.main.allow-circular-references=true

이 설정을 켜면 Spring은 예전처럼 순환 참조를 해결하려고 시도한다.

 

하지만 이 설정은 근본 해결책이 아니다. 해당 설정은 일단 애플리케이션을 실행시키기 위한 임시방편이다. 레거시 프로젝트를 Spring Boot 2.6 이상으로 마이그레이션하는 상황이라면 일시적으로 사용할 수는 있다. 하지만 장기적으로는 순환 참조 구조를 제거하는 방향으로 리팩토링해야 한다.

이 설정에 계속 의존하면 시스템 내부의 잘못된 의존 관계가 숨겨진 채로 남게 된다. 시간이 지날수록 구조는 더 복잡해지고, 나중에는 작은 변경 하나에도 여러 서비스가 연쇄적으로 영향을 받는 상황이 될 수 있다.

따라서 allow-circular-references=true는 문제를 해결하는 스위치가 아니라, 리팩토링 시간을 벌기 위한 임시 안전장치로만 사용하는 것이 바람직하다.

 

📝 순환 참조가 발생하는 대표적인 설계 문제

순환 참조는 우연히 발생하지 않는다. 대부분 객체 간 책임이 명확하지 않거나, 도메인 경계가 흐릿할 때 발생한다.

대표적인 예시를 보자.

@Service
public class UserService {
    private final OrderService orderService;

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

    public void withdraw(Long userId) {
        if (orderService.hasActiveOrders(userId)) {
            throw new IllegalStateException("진행 중인 주문이 있어 탈퇴할 수 없습니다.");
        }
        // 회원 탈퇴 처리
    }
}
@Service
public class OrderService {
    private final UserService userService;

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

    public void createOrder(Long userId) {
        userService.validateUserStatus(userId);
        // 주문 생성 처리
    }
}

위 구조에서 UserService는 회원 탈퇴를 위해 주문 정보를 확인해야 한다. 반대로 OrderService는 주문 생성을 위해 회원 상태를 검증해야 한다.

말만 들었을 때는 굉장히 자연스러워 보인다. 하지만 구조적으로 들여다 보면 두 서비스가 서로의 도메인 로직에 깊게 관여하고 있다. UserService는 회원 관리에 집중해야 하고, OrderService는 주문 관리에 집중해야 한다. 그런데 서로의 내부 기능을 직접 호출하면서 양방향 의존성이 생긴다. 이런 구조는 시간이 지날수록 유지보수가 어려워진다.

 

🚥 해결책 1: @Lazy 애노테이션 사용

가장 간단한 우회 방법 중 하나는 @Lazy를 사용하는 것이다.

@Service
public class OrderService {
    private final UserService userService;

    public OrderService(@Lazy UserService userService) {
        this.userService = userService;
    }
}

@Lazy를 사용하면 Spring은 실제 UserService 객체를 즉시 주입하지 않고, 프록시 객체를 대신 주입한다. 실제 UserService는 해당 메서드가 처음 호출되는 시점에 생성된다. 이렇게 하면 애플리케이션 시작 시점의 순환 참조 문제를 우회할 수 있다.

하지만 @Lazy 애노테이션 역시 근본 해결책은 아니다. 문제 발생 시점이 애플리케이션 시작 단계에서 런타임으로 미뤄질 수 있다. 또한 실제 메서드가 처음 호출되는 순간 객체 초기화가 발생하면서 예상치 못한 지연이 생길 수도 있다.

따라서 @Lazy는 리팩토링이 당장 어려운 레거시 코드에서 제한적으로 사용하는 것이 좋다.

 

🚚 해결책 2: ObjectProvider 사용

ObjectProvider를 사용하면 필요한 Bean을 즉시 주입받는 대신, 필요한 시점에 직접 조회할 수 있다.

@Service
public class OrderService {
    private final ObjectProvider<UserService> userServiceProvider;

    public OrderService(ObjectProvider<UserService> userServiceProvider) {
        this.userServiceProvider = userServiceProvider;
    }

    public void createOrder(Long userId) {
        UserService userService = userServiceProvider.getIfAvailable();
        if (userService == null) {
            throw new IllegalStateException("UserService를 사용할 수 없습니다.");
        }

        userService.validateUserStatus(userId);
        // 주문 생성 처리
    }
}

이 방식도 결국 의존성 주입 시점을 늦추는 것이다. OrderService를 만들 때 UserService 자체를 주입받는 것이 아니라, 나중에 UserService를 찾아올 수 있는 공급자 객체를 주입받는다.

@Lazy보다 의존성을 가져오는 시점이 코드에 명시적으로 드러난다는 장점이 있지만 이 방식 역시 Spring의 ObjectProvider라는 프레임워크 API에 도메인 코드가 의존하게 된다. 따라서 근본적인 설계 개선보다는 전술적 우회 방법으로 보는 것이 좋다.

 

📡 해결책 3: 세터 주입 또는 필드 주입으로 변경

순환 참조를 우회하기 위해 생성자 주입을 세터 주입이나 필드 주입으로 바꾸는 방법도 있다.

@Service
public class UserService {
    private OrderService orderService;

    @Autowired
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}

이 방식은 객체를 먼저 생성한 뒤 나중에 의존성을 주입할 수 있기 때문에 일부 상황에서는 순환 참조를 우회할 수 있다.

하지만 이 방법도 권장하기 어렵다. 생성자 주입을 사용하면 필수 의존성을 객체 생성 시점에 강제할 수 있고, 필드를 final로 선언할 수 있다. 반면 세터 주입이나 필드 주입은 객체가 불완전한 상태로 존재할 가능성을 만든다.

또한 테스트 코드에서도 의존성이 명확하게 드러나지 않는다. 따라서 세터 주입이나 필드 주입은 순환 참조를 해결하기 위한 근본적인 방법이 아니라, 오히려 문제를 숨기는 방식에 가깝다.

 

👨🏻‍🔧 해결책 4: 공통 책임을 제3의 서비스로 분리(권장)

순환 참조를 가장 건강하게 해결하는 방법은 책임을 다시 나누는 것이다. 앞에서 본 예시에서 UserServiceOrderService가 서로를 참조하는 이유는 아래와 같다.

  • 주문 생성 시 회원 상태 검증이 필요하다.
  • 회원 탈퇴 시 진행 중인 주문 여부 확인이 필요하다.

이때 두 서비스가 서로를 직접 호출하게 두는 대신, 공통으로 필요한 책임을 별도 서비스로 분리할 수 있다.

 

예를 들어 회원 검증 로직을 UserValidator로 분리한다.

@Component
public class UserValidator {
    public void validateUserStatus(Long userId) {
        // 회원 상태 검증
    }
}

 

그리고 OrderServiceUserService가 아니라 UserValidator에 의존한다.

@Service
public class OrderService {
    private final UserValidator userValidator;

    public OrderService(UserValidator userValidator) {
        this.userValidator = userValidator;
    }

    public void createOrder(Long userId) {
        userValidator.validateUserStatus(userId);
        // 주문 생성 처리
    }
}

또는 주문 상태 확인 로직을 별도의 OrderReader, OrderValidator 같은 컴포넌트로 분리할 수도 있다.

@Component
public class OrderValidator {
    public boolean hasActiveOrders(Long userId) {
        // 진행 중인 주문 여부 확인
        return false;
    }
}
@Service
public class UserService {
    private final OrderValidator orderValidator;

    public UserService(OrderValidator orderValidator) {
        this.orderValidator = orderValidator;
    }

    public void withdraw(Long userId) {
        if (orderValidator.hasActiveOrders(userId)) {
            throw new IllegalStateException("진행 중인 주문이 있어 탈퇴할 수 없습니다.");
        }
        // 회원 탈퇴 처리
    }
}

 

이렇게 하면 UserServiceOrderService가 서로 직접 의존하지 않아도 된다.

Before
UserService → OrderService
OrderService → UserService

After
UserService → OrderValidator
OrderService → UserValidator

순환 구조가 단방향 구조로 바뀐다.

 

📨 해결책 5: 이벤트 기반으로 분리(권장)

두 서비스가 반드시 즉시 서로를 호출할 필요가 없다면, 이벤트 기반 구조를 사용할 수도 있다. 예를 들어 주문이 생성된 뒤 회원 관련 후처리가 필요하다고 하자.

이때 OrderServiceUserService를 직접 호출하는 대신, “주문이 생성되었다”는 이벤트를 발행할 수 있다.

public record OrderCreatedEvent(Long orderId, Long userId) {
}
@Service
public class OrderService {
    private final ApplicationEventPublisher eventPublisher;

    public OrderService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void createOrder(Long userId) {
        // 주문 생성 처리
        Long orderId = 1L;

        eventPublisher.publishEvent(new OrderCreatedEvent(orderId, userId));
    }
}

 

그리고 UserService 또는 별도의 이벤트 핸들러가 해당 이벤트를 구독한다.

@Component
public class OrderEventHandler {

    @EventListener
    public void handle(OrderCreatedEvent event) {
        // 주문 생성 이후 필요한 후처리
    }
}

이 구조에서는 OrderServiceUserService를 직접 알 필요가 없다. 단지 이벤트를 발행할 뿐이다. 이벤트를 처리하는 쪽도 OrderService를 직접 호출하지 않는다. 특정 이벤트가 발생하면 반응할 뿐이다.

따라서 두 객체 간의 결합도를 크게 낮출 수 있다. 다만 이벤트 기반 구조는 흐름이 눈에 바로 보이지 않을 수 있다. 메서드 호출처럼 “어디서 어디로 호출되는지” 가 명확하지 않기 때문에, 이벤트 이름과 핸들러 위치를 잘 관리해야 한다.

 

🔨 해결책 6: Facade 또는 애플리케이션 서비스 도입(권장)

여러 서비스의 로직을 조합해야 하는 경우, 각 서비스가 서로를 직접 호출하게 만들기보다 상위 계층의 조합 서비스를 둘 수 있다. 예를 들어 회원 탈퇴를 처리할 때 회원 정보도 확인해야 하고, 주문 상태도 확인해야 한다고 하자.

이때 UserServiceOrderService가 서로를 직접 호출하게 만들지 않고, UserWithdrawalFacade 같은 클래스를 둘 수 있다.

@Service
public class UserWithdrawalFacade {
    private final UserService userService;
    private final OrderService orderService;

    public UserWithdrawalFacade(UserService userService, OrderService orderService) {
        this.userService = userService;
        this.orderService = orderService;
    }

    public void withdraw(Long userId) {
        if (orderService.hasActiveOrders(userId)) {
            throw new IllegalStateException("진행 중인 주문이 있어 탈퇴할 수 없습니다.");
        }

        userService.withdraw(userId);
    }
}

파사드가 여러 서비스를 조합하고, 각 도메인 서비스는 자신의 책임에만 집중한다. 이 방식은 계층 간 의존성 방향을 정리하는 데 특히 유용하다.

 

👤 자기 자신을 주입받는 경우도 순환 참조일까?

특이한 경우로, Bean이 자기 자신을 주입받는 경우도 있다.

@Service
public class MyService {
    @Autowired
    private MyService self;

    public void methodA() {
        self.methodB();
    }

    @Transactional
    public void methodB() {
        // 트랜잭션 처리
    }
}

이런 코드는 보통 같은 클래스 내부에서 @Transactional 같은 AOP 기능을 적용하기 위해 사용된다.

Spring AOP는 프록시 기반으로 동작하기 때문에, 같은 클래스 내부에서 this.methodB()를 호출하면 프록시를 거치지 않는다. 그래서 트랜잭션이 적용되지 않을 수 있다. 이를 피하려고 자기 자신의 프록시를 주입받는 방식이 사용되기도 한다.

하지만 이것 역시 좋은 설계라고 보기는 어렵다. 트랜잭션 경계가 달라야 하는 메서드라면 별도의 서비스로 분리하는 것이 더 명확하다.

@Service
public class MyService {
    private final MyTransactionService myTransactionService;

    public MyService(MyTransactionService myTransactionService) {
        this.myTransactionService = myTransactionService;
    }

    public void methodA() {
        myTransactionService.methodB();
    }
}
@Service
public class MyTransactionService {

    @Transactional
    public void methodB() {
        // 트랜잭션 처리
    }
}

이렇게 분리하면 자기 자신을 주입받지 않아도 되고, 트랜잭션 경계도 더 명확해진다.


<참고 자료>

Spring Circular Dependency - Alibaba Cloud Community
How to Handle Circular Reference Errors in Spring
Spring Boot 2.6 Release Notes
Circular Dependencies in Spring - GeeksforGeeks

0개의 댓글