SpringBoot 순환 참조(Circular Dependency) 문제 해결

J_log·2025년 1월 14일
0

문제 발생

Spring Boot 프로젝트에서 OAuth2 로그인 구현 중, 어플리케이션이 실행되지 않고 순환 참조 문제가 발생했다.

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

┌─────┐
|  webConfig defined in file [/path/to/project/WebConfig.class]
↑     ↓
|  OAuth2AuthenticationSuccessHandler defined in file [/path/to/project/OAuth2AuthenticationSuccessHandler.class]
↑     ↓
|  OAuth2UserUnlinkManager defined in file [/path/to/project/OAuth2UserUnlinkManager.class]
↑     ↓
|  googleOAuth2UserUnlink defined in file [/path/to/project/GoogleOAuth2UserUnlink.class]
└─────┘

Spring Boot 2.6 이상에서는 순환 참조가 기본적으로 금지되어 있어 어플리케이션이 시작되지 않았다.

원인 추론

콘솔 화면을 확인해보면 친절하게도 어떻게 순환 참조가 발생하고 있는지 확인 할 수 있다.

  • 의존성 순환 구조 :
    여러 클래스 간 상호 의존성이 순환 구조를 형성했다.
    • WebConfing -> OAuth2AuthenticationSuccessHandler
    • OAuth2AuthenticationSuccessHandler -> OAuth2UserUnlinkManager
    • OAuth2UserUnlinkManager -> GoogleOAuth2UserUnlink
    • GoogleOAuth2UserUnlink -> WebConfig

각 클래스가 서로의 빈(Bean)을 주입받는 과정에서 순환 참조가 발생했다.

  • RestTemplateBuilder의 반복 사용 문제:
    프로젝트에서 여러 구성 요소가 동일한 방식으로 RestTemplateBuilder를 직접 주입받아 순환 참조 문제를 악화시켰다.

WebConfig 에서 RestTemplate를 생성자로 주입하고 있고,

OAuth2UserUnlink

Image 1

Image 2

Image 3

각각의 OAuth2UserUnlink 클래스에서 동일한 방식으로 주입.

해결 방법

  1. 팩토리 클래스 사용
  • RestTemplateBuilder를 여러 곳에서 직접 사용하지 않도록, 팩토리 클래스를 도입하여 RestTemplate 객체 생성을 통합
@Component
public class RestTemplateFactory {
    private final RestTemplateBuilder restTemplateBuilder;

    public RestTemplateFactory(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplateBuilder = restTemplateBuilder;
    }

    public RestTemplate create() {
        return restTemplateBuilder.build();
    }
}

이후 필요한 곳에서 RestTemplate를 직접 생성하는 대신 RestTemplateFactory의 create() 메서드를 호출하도록 수정

@Service
public class SomeService {
    private final RestTemplate restTemplate;

    public SomeService(RestTemplateFactory restTemplateFactory) {
        this.restTemplate = restTemplateFactory.create();
    }
}

  1. 구조 분리
  • 순환 참조를 끊기 위해 의존성을 재설계
    • GoogleOAuth2UserUnlink -> WebConfig로의 직접 참조를 제거
    • 의존성을 최소화하고 필요한 경우 인터페이스를 도입하여 간접적으로 주입하도록 변경
  1. @Lazy 적용
  • 일부 순환 참조가 불가피한 경우, 지연 로드(@Lazy)를 사용하여 의존성을 해결
@Service
public class OAuth2AuthenticationSuccessHandler {
    private final OAuth2UserUnlinkManager unlinkManager;

    public OAuth2AuthenticationSuccessHandler(@Lazy OAuth2UserUnlinkManager unlinkManager) {
        this.unlinkManager = unlinkManager;
    }
}
  1. Spring 설정 변경 (임시)
  • 문제를 디버깅하는 동안 application.properties에 설정을 추가하여 순환 참조 허용
spring.main.allow-circular-references=true

결과 확인

RestTemplateFactory를 도입함으로써 RestTemplateBuilder의 반복 사용을 제거하고, 순환 참조 문제를 해결할 수 있었다.
의존성 설계의 중요성과 해결 방안에 대한 좋은 학습 기회가 되었다.

2개의 댓글

comment-user-thumbnail
2025년 1월 14일

와 진짜 이건 노벨상 감입니다.

1개의 답글