1. 순환참조란?
순환참조는 맞물린 의존성 주입 (DI) 상태에서 어떤 빈을 먼저 생성할지 결정하지 못해서 생기에 발생한다. BeanA에서 BeanB를 참조(BeanA->BeanB) 일 경우 스프링은 BeanB를 먼저 생성 후 BeanA를 생성하기에, BeanB에서 다시 BeanA를 참조할 경우 (BeanA->BeanB->BeanA) 순환 참조가 발생하게된다.
2. 의존성 주입
의존성 주입의 3가지 상황 (생성자 주입방식, 필드 주입방식, Setter주입)에서 순환참조가 발생할수 있다. 다음 포스트 각각의 상세 내용을 확인할 수 있고, 이번 포스트에서는 각각의 경우에 순환참조가 발생하면 어떤 차이점이 있는지 확인해 보자.
2023.11.06 - [Spring] - [Spring] IoC(제어의 역전) & DI(의존성 주입)의 개념
▶ 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 형태로 참조가 순환되지 않도록 분리해야 한다.
'Spring' 카테고리의 다른 글
[Spring] Java 21 외장 톰캣 버전 설정 (1) | 2023.12.18 |
---|---|
[Spring] Spring Security6 filterchain 사용시 jsp 뷰 렌더링 설정 (0) | 2023.12.18 |
[Spring] 단위 테스트, JUnit의 개념 및 단위 테스트 코드 작성 방법 (0) | 2023.11.14 |
[Spring] 스프링 컨테이너(Spring container)의 개념 (1) | 2023.11.07 |
[Spring] IoC(제어의 역전) & DI(의존성 주입)의 개념 (0) | 2023.11.06 |