본문 바로가기

Framework/Spring

[Spring] 순환 의존성(Circular Dependency)에 대하여 - 컴도리 돌이

728x90

두 개 이상의 빈이 서로를 참조할 때 발생하는 문제로, 스프링 프레임워크에서는 애플리케이션 컨텍스트가 빈을 초기화할 때 문제가 발생합니다. 간단히 말해, 빈 A가 빈 B를 필요로 하고, 빈 B가 다시 빈 A를 필요로 하는 상황을 말해요. 이로 인해 애플리케이션이 제대로 시작되지 않거나 예기치 못한 런타임 오류가 발생할 수 있어요. 

 

순환 의존성 문제가 발생할 때 보통 다음과 같은 에러 로그를 볼 수 있어요 🤔

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

 

 

이런 에러 메시지는 빈 생성 과정에서 순환 참조가 발견되었음을 알려줍니다. 문제가 발생하는 코드를 보면서 확인해 볼게요.🤨

@Component
public class ServiceA {
    private final ServiceB serviceB;

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

@Component
public class ServiceB {
    private final ServiceA serviceA;

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

 

위에 코드에서는 ServiceAServiceB가 서로를 의존하고 있어 순환 의존성이 발생해요. Spring 컨테이너가 이 두 빈을 생성하는 데 어려움을 겪게 되죠. 지금은 코드길이가 짧아서 두 클래스 간에 사용이 이상하게 보이지만, 두 클래스가 서로 의존하는 일은 정말 흔하게 볼 수 있을 거예요 😂  그렇다면 어떻게 해결해야 할까요? 🤔


1. @Lazy 어노테이션 사용

@Lazy 어노테이션을 사용하여 지연 초기화를 적용하면, 빈이 필요할 때까지 초기화를 미룰 수 있습니다. 이를 통해 순환 의존성 문제를 해결할 수 있죠. 

@Component
public class ServiceA {
    private final ServiceB serviceB;

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

@Component
public class ServiceB {
    private final ServiceA serviceA;

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

 

stack overflow에서 동일한 문제가 발생했을 때, 역시나 첫 번째 high score를 가진 댓글이 @Lazy를 사용하여 해결하는 방안이네요 👍

 


2. 빈의 구조 재설계

두 번째 방안으로는 빈의 의존 관계를 재설계하여 순환 의존성을 피하는 것이 가장 확실한 방법입니다. 주입할 필요가 없는 부분을 분리하거나, @PostConsturct를 이용해 초기화를 지연시키는 등의 방법을 사용할 수 있죠. 

@Component
public class ServiceA {
    private ServiceB serviceB;

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

    @PostConstruct
    public void initialize() {
        serviceB.doSomething();
    }
}

@Component
public class ServiceB {
    private final ServiceA serviceA;

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

    public void doSomething() {
        serviceA.initialize();
    }
}

 

@PostConstruct를 사용하여 ServiceA의 초기화를 빈 생성 이후로 지연시키면, 빈이 순차적으로 초기화되므로 순환 의존성을 피할 수 있어요. 놀랍게도 두 번째로 인기 있는 해결 방안이 역시 @PostConstruct를 사용해서 빈의 순서를 조작하는 방법이네요 😆

 


 

순환 의존성은 작은 실수로 인해 발생할 수 있지만, 문제의 핵심을 이해하고 적절한 방법을 적용하면 쉽게 해결할 수 있는 문제 같아요. 중간 계층을 도입하거나, 초기화를 지연시키는 전략은 흔한 해결책으로, 개발하다가 위와 같은 에러가 발생하면 "아 빈이 문제가 있구나", "빈이 서로 의존하고 있구나"라고 여유롭게 인지하고 해결할 수 있을 거 같네요 ☕️


 

Requested bean is currently in creation: Is there an unresolvable circular reference?

i am using spring 3, and i have two beans of view scope: 1- Bean1: @Component("bean1") @Scope("view") public class Bean1 { @Autowired private Bean2 bean2; } 2- Bean2: @Component("bean2") @Sco...

stackoverflow.com

 

Aspect Oriented Programming with Spring :: Spring Framework

Aspect-oriented Programming (AOP) complements Object-oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enabl

docs.spring.io