개발을 하다 보면 “순환참조”라는 용어를 자주 접하게 됩니다. 특히 Spring Framework, NestJS 같은 의존성 주입(DI) 기반 프레임워크를 사용할 때 종종 순환참조 오류로 애플리케이션이 실행되지 않거나, 직렬화 에러가 발생하기도 하죠.
이번 글에서는 순환참조의 개념과 발생 원인, 위험성 그리고 실무에서 어떻게 해결할 수 있는지까지 정리해보겠습니다.
순환참조란?
순환참조(Circular Reference)는 두 개 이상의 객체나 모듈이 서로를 참조하면서 순환 구조를 이루는 것을 말합니다. 예를 들어 클래스 A가 클래스 B를 참조하고, 클래스 B가 다시 A를 참조하는 구조라면 이것이 순환참조입니다.
class A {
B b;
public A(B b) {
this.b = b;
}
}
class B {
A a;
public B(A a) {
this.a = a;
}
}
겉보기엔 간단한 구조지만, 이처럼 생성자에서 서로를 의존하게 되면 실제 실행 시 문제가 발생할 수 있습니다. 특히 스프링에서는 순환참조가 있으면 애플리케이션이 아예 기동되지 않기도 합니다.
순환참조의 위험성
순환참조는 단순한 코드 문제를 넘어서 애플리케이션의 안정성과 성능에 직접적인 영향을 미칠 수 있습니다. 주요 위험성은 다음과 같습니다.
- 무한 루프 발생: 잘못된 호출로 인해 재귀적으로 반복되는 로직이 발생할 수 있습니다.
- 메모리 누수(Leak): 객체들이 서로를 참조하고 있으면 GC(가비지 컬렉터)가 메모리를 해제하지 못해 누수가 발생할 수 있습니다.
- 직렬화 오류: 예를 들어 Jackson으로 객체를 JSON으로 변환할 때 무한 참조로 인해 StackOverflowError가 발생할 수 있습니다.
- 앱 실행 실패: Spring에서는 생성자 주입 시 순환참조가 발생하면 애플리케이션이 아예 실행되지 않고 에러를 출력합니다.
- 구조 파악 어려움: 코드 구조가 복잡해지고, 유지보수 시 의존관계를 파악하기 힘들어집니다.
순환참조가 발생하는 일반적인 상황
- 생성자 주입
생성자에서 서로를 의존할 때 가장 쉽게 순환참조가 발생합니다. - 양방향 연관관계 (ORM, JPA)
엔티티 간 @OneToMany / @ManyToOne 등의 양방향 관계를 설정할 때 순환참조 이슈가 발생하기 쉽습니다. - 모듈 간 import 순환 (TypeScript, Python 등)
A.ts에서 B.ts를 import하고, 다시 B.ts에서 A.ts를 import할 경우 import 순환 문제가 발생합니다.
순환참조 해결 방법
1. 생성자 주입 대신 Setter 주입 또는 @Lazy 사용
Spring에서는 생성자 대신 Setter 또는 필드 주입(@Autowired)을 사용하거나, @Lazy 어노테이션으로 참조 시점을 지연시켜 순환을 피할 수 있습니다.
@Component
public class A {
private B b;
@Autowired
public void setB(@Lazy B b) {
this.b = b;
}
}
2. 설계 개선
가장 근본적인 해결책은 구조 자체를 개선하는 것입니다.
예를 들어 A와 B가 서로를 직접 참조하지 않고, 인터페이스나 중간 매개체(예: Mediator)를 통해 간접적으로 연결되도록 설계합니다.
3. Jackson 순환참조 어노테이션 활용
Spring에서 JSON 직렬화 시 순환참조로 인한 오류를 방지하려면 @JsonManagedReference 와 @JsonBackReference 를 사용할 수 있습니다.
@JsonManagedReference
private List<Comment> comments;
@JsonBackReference
private Post post;
4. 모듈 분리 및 의존성 구조 개선 (TypeScript 등)
import 루프가 발생하는 경우, 공통 모듈로 분리하거나 모듈 간 의존성을 재정의해야 합니다. index.ts 파일을 잘 정리하는 것만으로도 순환참조를 줄일 수 있습니다.
마무리하며
순환참조는 단순한 버그 이상의 문제를 유발할 수 있는 중요한 이슈입니다. 특히 프레임워크에 대한 이해 없이 구조를 짜다 보면 어느 순간 앱이 실행되지 않거나, 이유 없는 직렬화 오류가 발생할 수 있습니다.
순환참조를 예방하려면 ‘의존 관계는 단방향이 기본’이라는 원칙을 기억하고, 구조적으로 책임과 역할을 분리하는 설계 습관을 들이는 것이 중요합니다.
'개발 > 좋은 개발 습관' 카테고리의 다른 글
MyBatis에서 selectKey 사용은 왜 조심해야 할까? (2) | 2025.05.08 |
---|---|
데이터 비교는 어떻게 해야 할까? – 성능을 살리는 DB 쿼리 작성법 (0) | 2025.05.08 |