IT개발및프로그래밍/웹서버프로젝트

스프링 순환 의존성이 발생하는 이유

devgodmj 2024. 10. 22. 17:32

순환 의존성 (Circular Dependency)

순환 의존성(Circular Dependency)이란 두 개 이상의 빈(Bean) 또는 객체가 서로를 의존할 때 발생하는 문제를 말합니다. 예를 들어, A 객체가 B 객체를 의존하고 B 객체가 다시 A 객체를 의존하는 경우, 순환 의존성이 발생합니다. 이는 빈을 생성하는 시점에서 스프링이 의존성을 해결하지 못하고 무한 루프에 빠지게 되어 애플리케이션이 정상적으로 실행되지 않게 만들 수 있습니다.

스프링은 대부분의 경우 순환 의존성을 해결할 수 있지만, 특정 상황에서는 순환 의존성이 해결되지 않을 수 있으며, 이러한 경우에는 코드 구조를 수정하거나 다른 방법으로 문제를 해결해야 합니다.

순환 의존성의 예시

아래는 ServiceA와 ServiceB 두 클래스가 서로를 의존하는 상황에서 순환 의존성이 발생하는 예시입니다.

1. 순환 의존성 예시

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ServiceA {

    private final ServiceB serviceB;

    @Autowired
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void doSomething() {
        System.out.println("ServiceA is doing something...");
    }
}

@Component
public class ServiceB {

    private final ServiceA serviceA;

    @Autowired
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void doSomethingElse() {
        System.out.println("ServiceB is doing something else...");
    }
}

 

설명

  1. ServiceA는 ServiceB를 의존하고, ServiceB는 다시 ServiceA를 의존하고 있습니다.
  2. 스프링이 빈을 생성할 때 ServiceA 빈을 생성하려면 먼저 ServiceB 빈이 필요하지만, ServiceB 빈을 생성하려면 ServiceA 빈이 필요하기 때문에 이 과정에서 순환 의존성이 발생합니다.
  3. 이로 인해 스프링이 두 빈을 생성하지 못하고 예외를 발생시키게 됩니다.

2. 순환 의존성 발생 시 오류 메시지

스프링이 순환 의존성을 해결하지 못하면 아래와 같은 오류 메시지가 발생할 수 있습니다:

Error creating bean with name 'serviceA': Requested bean is currently in creation:
 Is there an unresolvable circular reference?

 

이 오류는 ServiceA와 ServiceB가 서로 무한히 의존하며 빈 생성 순서가 해결되지 않아서 발생합니다.

 

순환 의존성을 해결하는 방법

순환 의존성은 보통 구조적인 문제를 나타내며, 이를 해결하기 위해서는 객체 간의 의존성을 명확히 정의하고, 설계 구조를 재검토하는 것이 좋습니다. 스프링에서는 순환 의존성을 해결하기 위해 몇 가지 방법을 제공합니다.

1. 필드 주입을 사용한 순환 의존성 해결

스프링에서는 필드 주입(Field Injection)을 사용할 때, 내부적으로 @Autowired 애너테이션이 빈의 초기화를 지연시키는 방식으로 순환 의존성을 해결할 수 있습니다. 아래는 필드 주입을 사용한 순환 의존성 해결 예시입니다.

 

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ServiceA {

    @Autowired
    private ServiceB serviceB;

    public void doSomething() {
        System.out.println("ServiceA is doing something...");
    }
}

@Component
public class ServiceB {

    @Autowired
    private ServiceA serviceA;

    public void doSomethingElse() {
        System.out.println("ServiceB is doing something else...");
    }
}

 

설명

  • 필드 주입을 사용하면 스프링이 객체를 생성한 후에 필드에 대한 의존성을 주입하므로, 순환 의존성 문제가 발생하지 않습니다.
  • 그러나 필드 주입 방식은 권장되는 방법은 아닙니다. 왜냐하면 테스트가 어렵고 객체의 불변성 유지가 어렵기 때문입니다.

2. @Lazy 애너테이션 사용

스프링에서 제공하는 @Lazy 애너테이션은 객체의 생성 시점을 지연시켜 순환 의존성을 해결할 수 있는 방법입니다. @Lazy를 사용하면 필요한 시점에 객체를 생성하여 순환 의존성을 방지할 수 있습니다.

 

@Lazy 예시

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class ServiceA {

    private final ServiceB serviceB;

    @Autowired
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void doSomething() {
        System.out.println("ServiceA is doing something...");
    }
}

@Component
public class ServiceB {

    private final ServiceA serviceA;

    @Autowired
    public ServiceB(@Lazy ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void doSomethingElse() {
        System.out.println("ServiceB is doing something else...");
    }
}

 

설명

  • @Lazy 애너테이션은 스프링이 빈을 생성하는 시점을 지연시켜 순환 의존성을 해결할 수 있습니다.
  • 이 방법을 사용하면 스프링은 객체를 즉시 생성하는 대신, 해당 객체가 실제로 필요할 때까지 빈의 생성을 미룹니다.
  • 이 방식은 순환 의존성을 해결할 수 있는 강력한 도구입니다.

3. Setter 주입을 사용하여 순환 의존성 해결

또 다른 방법은 Setter 메서드를 이용한 주입 방식입니다. 이 방법은 빈이 생성된 후에 의존성을 주입하는 방식으로, 순환 의존성 문제를 해결할 수 있습니다.

Setter 주입 예시

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class ServiceA {

    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void doSomething() {
        System.out.println("ServiceA is doing something...");
    }
}

@Component
public class ServiceB {

    private ServiceA serviceA;

    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public void doSomethingElse() {
        System.out.println("ServiceB is doing something else...");
    }
}

 

설명

  • Setter 주입은 빈이 생성된 후에 의존성을 설정할 수 있으므로 순환 의존성 문제를 피할 수 있습니다.
  • 이 방법도 순환 의존성 문제를 해결하는 데 유용하지만, 객체가 완전하게 초기화되기 전에 의존성이 주입될 수 있어 불완전한 상태가 될 위험이 있습니다.

4. 구조 재설계

순환 의존성은 보통 객체 간의 결합도가 높거나 설계가 복잡할 때 발생합니다. 이 문제를 해결하기 위한 근본적인 방법은 객체 간의 결합도를 줄이고 구조를 재설계하는 것입니다.

해결 방법:

  1. 중재자 패턴(Mediator Pattern): 두 클래스 간에 직접적인 의존성을 줄이고, 중재자를 두어 간접적으로 상호작용하도록 설계합니다.
  2. 인터페이스 분리: 두 클래스가 서로 직접 의존하지 않고, 인터페이스를 통해 간접적으로 의존하도록 구조를 변경합니다.