기술과 산업/언어 및 프레임워크

Spring Framework 시리즈 6화 – 의존성 순환 오류 해결 전략

B컷개발자 2025. 5. 27. 19:05
728x90

스프링에서 발생할 수 있는 대표적인 구조적 문제, 의존성 순환 오류(Dependency Circular Reference)의 원인과 해결 방법을 예제 중심으로 정리합니다. @Lazy, 세터 주입, 구조 분리 등 실전 대응 전략도 함께 제공합니다.

순환 의존성이란 무엇인가?

의존성 주입이란 객체 간의 관계를 외부에서 설정해주는 것을 말합니다. 그런데 두 객체가 서로를 참조하면, 객체 생성 시점에서 무한 루프와 같은 문제가 발생하게 됩니다.

예를 들어 A → B, B → A가 동시에 일어날 경우, 스프링은 어느 쪽을 먼저 만들어야 할지 결정할 수 없습니다.


예제 – 순환 의존성 발생

@Component
public class A {
    private final B b;

    public A(B b) {
        this.b = b;
    }
}

@Component
public class B {
    private final A a;

    public B(A a) {
        this.a = a;
    }
}

이 코드는 org.springframework.beans.factory.UnsatisfiedDependencyException을 발생시킵니다.

왜 발생하는가?

  • A를 생성하려면 B가 먼저 필요함
  • B를 생성하려면 다시 A가 필요함
  • 생성자 주입(순수 생성자 기반)에서는 객체 생성 시점에 완전한 의존성 확보가 요구되기 때문에 순환이 불가능

해결 방법 ① – 세터 주입 또는 필드 주입으로 변경

@Component
public class A {
    private B b;

    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}
  • 생성자 주입은 즉시 주입을 요구하지만
  • 세터/필드 주입은 Spring이 일단 빈을 등록한 뒤, 순차적으로 주입하므로 순환 처리가 가능

❗ 단점: 필수 의존성이라는 보장이 깨지므로 설계 측면에서 유연성은 높지만 안정성은 낮아짐


해결 방법 ② – @Lazy를 활용한 지연 주입

@Component
public class A {
    private final B b;

    public A(@Lazy B b) {
        this.b = b;
    }
}
  • @Lazy는 주입 시점이 아닌 실제 호출 시점에 객체를 생성
  • Spring이 순환 관계에 있는 두 빈을 임시로 등록한 뒤, 프록시를 통해 지연 처리

✅ 실무에서는 생성자 주입을 유지하면서 순환을 해소할 수 있는 가장 안정적인 방법


해결 방법 ③ – 구조를 리팩토링하여 의존 분리

가장 권장되는 방법은 두 컴포넌트의 의존 관계 자체를 끊는 것입니다.

예시: 공통 인터페이스 또는 서비스 계층을 분리

public interface Mediator {
    void doSomething();
}

@Component
public class MediatorImpl implements Mediator {
    private final A a;
    private final B b;

    public MediatorImpl(A a, B b) {
        this.a = a;
        this.b = b;
    }

    public void doSomething() {
        // a와 b의 공통 기능 조율
    }
}
  • 직접 A ↔ B 구조를 제거하고, 중재자(Mediator)를 통해 간접 의존 구조로 바꿈
  • 도메인 간 결합도를 낮추고, 유닛 테스트도 쉬워짐

실무 사례 – 순환 의존성 발생 위치

발생 위치 원인

서비스 → 리포지토리 → 서비스 동일한 서비스가 여러 컴포넌트에서 호출되며 참조될 때
AOP 적용된 클래스 ↔ 트랜잭션 관리 빈 Proxy Bean이 적용된 상황에서 DI 충돌
@Transactional → @Async → 내부 DI 호출 프록시 기반 빈이 순환될 때 발생 가능성 높음

순환 참조를 강제로 허용하는 설정 (비추천)

Spring Boot 2.6부터는 순환 참조가 기본적으로 금지되어 있습니다.

spring.main.allow-circular-references=true
  • 단기 해결에는 도움이 되지만, 결합도를 높이고 설계 결함을 은폐하므로 비추천
  • 테스트용 환경이나 급한 배포 이전에만 일시적으로 고려 가능

마무리 – 순환 참조는 구조 설계의 시그널이다

순환 참조는 단순한 코드 오류가 아니라 설계의 방향이 잘못되었음을 알리는 구조적 시그널입니다.

실제 현업에서도 순환 참조를 발견하면 무조건 @Lazy나 세터 주입으로 해결하기보다는 다음과 같은 순서로 접근합니다:

  1. 진짜 필요한 의존성인지 검토
  2. 중재 서비스 혹은 인터페이스로 리팩토링 가능성 확인
  3. 도메인 간 책임 분리 재설계
  4. 그럼에도 불가피하다면 @Lazy, 세터 주입으로 회피

다음 7화에서는 @Value와 Environment 객체를 활용해 외부 설정값을 주입하는 방법을 실습합니다. 프로퍼티 파일을 사용하는 이유, 스프링 프로파일과의 연계, 보안 정보를 안전하게 주입하는 전략까지 함께 다룹니다.

728x90