필자도 백엔드 첫 프로젝트하면서 순환참조 문제를 겼었었다 ……. 그 당시에는 첫 프로젝트인데 기간은 촉박하고 다른 기술들도 처음 써보는 게 많다보니.. 공부해야할 것도 너무너무 많았고 ..
결국 급한대로 @Lazy 애노테이션을 사용해서 임의로 해결 했지만, 나중에 근본적인 순환참조 고리를 끊어내기 위해 리팩토링으로 해결했었다. 설계 자체에 시간을 많이 투자해야된다는 건 알았지만 조급한 마음에 중요성을 간과하고 시작해서 그런 거 같다. 진짜 설계랑 흐름 정리가 제일 중요 !!!!
늦었지만 어떤 현상인지 정리해보려고한다.
순환 참조 문제란?
- A 클래스가 B 클래스의 Bean을 주입받고, B 클래스가 A 클래스의 Bean을 주입받는 상황처럼 서로 순환되어 참조할 경우 발생하는 문제.
왜 발생하는걸까?
- 먼저, 특정 클래스에서 IoC 컨테이너에 있는 Bean을 주입받기 위해서 세 가지 방법을 사용할 수 있습니다.
- 필드 주입, Setter 주입, 생성자 주입방식
- 이때, 생성자 주입방식과 나머지 두 가지 주입 방식에서 순환참조문제가 조금 다르게 발생합니다.
필드 주입방식과 Setter 주입방식에서의 순환참조문제
애플리케이션 실행 과정에서는 예외가 발생하지 않는다.
문제가 되는 순간은 실제로 해당 메서드가 호출되었을 때이다.
이 상황은 순환참조 문제가 아니라, 서로 다른 메서드가 서로 호출을 할 떄 생기는 순환호출 문제이다.
즉, 스프링 애플리케이션 로딩시에는 예외가 발생하지 않는다.
단순히 클래스끼리 순환참조하는 것이 아니라
실제로 메서드가 순환호출
되어야 하고,해당 메서드가 호출되는 시점에 예외가 발생
한다.
생성자 주입방식에서의 순환참조문제
상황
스프링 애플리케이션이 로딩되는 시점에 A클래스가 B클래스를 의존하고 B클래스가 C클래스를 의존한다면 Spring Boot는 A클래스에 대한 Bean을 만들기 위해서 B클래스의 Bean을 주입하는데 없으니까, B클래스의 Bean을 먼저 만든다. 근데 그 과정에서 또 C클래스의 Bean을 주입하는데 없으니까 C클래스의 Bean을 먼저 만든다.
결과적으로 Spring Boot는 C - B - A 순서로 Bean을 생성하는 것이다.
그렇다면? A클래스가 B클래스를 의존하고, B클래스가 A클래스를 의존하는 상황에 대해서 이야기해보자.
A클래스의 Bean을 만드는 과정에서 B클래스의 Bean을 주입하고, 없으니까 B클래스의 Bean을 먼저 생성한다. 이 때 A클래스의 Bean을 주입하려는데 없으니까 A클래스의 Bean을 먼저 생성한다. 이 떄 B클래스의 Bean을 주입하려는데 없으니까 A클래스의 Bean을 먼저 생성 …… 무한 반복
결과적으로 어떠한 Bean도 생성하지 못하는 문제가 발생하는데, 이를 바로 “순환참조 문제”라고 한다.
- 클래스가 서로 의존성 주입을 통해 순환참조하고 있을 때 발생하는 문제이다. (메서드까지 도달도 못 함.)
스프링 애플리케이션 로딩시점에서 예외가 발생한다.
- 즉, 스프링 애플리케이션 로딩도 되지 않는다.
어떻게 해결할까?
가장 좋은 건 설계상 이렇게 순환참조문제가 발생할 수 있는 구조잧제를 만들지 않는 것이 가장 좋다.
하지만, 순환의 고리를 끊을 수 없는 경우에는 @Lazy 애노테이션을 사용해 임의로 해결
이 가능하다. (필자가 첫 프로젝트때 사용했던 방법 ………)
// @Lazy 사용
@Service
public class ServiceA {
private final ServiceB serviceB;
@Autowired
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
private final ServiceA serviceA;
@Autowired
public ServiceB(@Lazy ServiceA serviceA) {
this.serviceA = serviceA;
}
}
근데 당연히 권장되지 않습니다.
애플리케이션 로딩시점이 아니라 해당 Bean이 필요한 시점에 주입을 받기 때문에 특정 HTTP 요청을 받았을 때 Heap 메모리가 증가할 수 있으며 메모리가 충분하지 않을 경우에는 장애가 발생할 수 있습니다.
또 다른 해결방법은 필드 주입방식 혹은 Setter를 이용한 주입방식을 이용하는 것
입니다.
그렇지만, 순환참조되는 설계를 지양하는 것이 제일 좋다 !!!
생성자 주입방식의 장점중의 하나도 이러한 순환참조 여부를 애플리케이션 로딩 시점에서 알 수 있다는 점에서 비춰 봤을 때 아주 불가피한 상황이 아니라면 설계를 개선하는 것이 제일 베스트.
결론: 설계를 잘하자 …
'공부' 카테고리의 다른 글
[Java] 객체지향 설계의 5원칙 - SOLID (0) | 2025.04.07 |
---|---|
[Spring] spring의 의존성 주입 방법 4가지 (0) | 2025.04.04 |
[Spring] POJO란? Hibernate와 Spring이 왜 POJO 친화적인가 (0) | 2025.04.03 |
[DDD] Aggregate란? (0) | 2025.04.01 |
[JPA] JPA의 N + 1 문제 정리 (0) | 2025.03.27 |