[Spring] 순환참조란? The dependencies of some of the beans in the application context form a cycle

출처: https://ch4njun.tistory.com/269

1. 순환참조란?

순환참조는 맞물린 의존성 주입 (DI) 상태에서 어떤 빈을 먼저 생성할지 결정하지 못해서 생기에 발생한다. BeanA에서 BeanB를 참조(BeanA->BeanB) 일 경우 스프링은 BeanB를 먼저 생성 후 BeanA를 생성하기에, BeanB에서 다시 BeanA를 참조할 경우 (BeanA->BeanB->BeanA) 순환 참조가 발생하게된다. 

2. 의존성 주입 

의존성 주입의 3가지 상황 (생성자 주입방식, 필드 주입방식, Setter주입)에서 순환참조가 발생할수 있다. 다음 포스트 각각의 상세 내용을 확인할 수 있고, 이번 포스트에서는 각각의 경우에 순환참조가 발생하면 어떤 차이점이 있는지 확인해 보자.

2023.11.06 - [Spring] - [Spring] IoC(제어의 역전) & DI(의존성 주입)의 개념

 

[Spring] IoC(제어의 역전) & DI(의존성 주입)의 개념

1. IoC (Inversion of Control) 제어의 역전 IoC란 메인 프로그램에서 컨테이너나 프레임워크로 객체와 객체의 의존성에 대한 제어를 넘기는 것을 말한다. 프레임워크 없이 개발할 때는 각 객체에 대한

junhkang.tistory.com

▶ 2-1. 생성자 주입

@Component
public class BeanA {
	private BeanB beanB;

	public void BeanA(BeanB beanB){
		this.beanB = beanB;
	}
}

@Component
public class BeanB {
	private BeanA beanA;

	public void BeanB(BeanA beanA){
		this.beanA = beanA;
	}
}

 

생성자 주입의 경우, 애플리케이션 구동 시 스프링 컨테이너(IOC)는 BeanA 빈을 생성하기 위해 BeanB를 찾고 BeanB를 찾기 위해 Bean A를 찾기 때문에 순환참조가 발생하게 된다.

 2-2. 필드 주입, Setter 방식

필드 주입, Setter 방식은 애플리케이션의 실행 시점에서는 에러가 발생되지 않는다. 어플리케이션의 실행 시점이 아닌, 실제로 사용되는 시점에 실행되는 메서드가 순환 호출되기 때문이다. 필요 없는 시점에는 null 상태로 유지 후 사용될 때 의존성이 주입되며 참조되기 시작한다.

3. 해결책

 3-1. @Lazy 어노테이션

@Component
public class BeanA {
	private BeanB beanB;

	public void BeanA(BeanB beanB){
		this.beanB = beanB;
	}
}

@Component
public class BeanB {
	private BeanA beanA;

	public void BeanB(@Lazy BeanA beanA){
		this.beanA = beanA;
	}
}

 

다음과 같이 @Lazy 어노테이션을 통해 시점을 지연시킬 수 있으나 스프링에서는 이 방식을 추천하지 않는다. 애플리케이션 로딩시점이 아닌 Bean이 필요한 시점에 주입받기 때문에 특정 HTTP 요청을 받을 때 Heap 메모리가 증가할 수 있으며 메모리가 충분하지 않은 경우 장애로 이어질 수 있다. 또한 잘못된 빈의 생성시점을 늦추기에 문제상황에 대한 인식이 늦어질 수 있다.

 3-2. 설계 변경

근본적으로 순환참조가 일어나지 않는 설계를 해야 한다.

단순하게는 BeanA -> BeanB-> BeanA의 관계를 BeanA -> BeanB -> BeanC 형태로 참조가 순환되지 않도록 분리해야 한다.