Spring 6 순환 의존성과 조기 프록시: 3단계 캐시의 실제 한계
기준 환경: Spring Framework 6, Spring Boot 3. 이 문서는 특정 프록시 불일치 사례를 설명하며, 결론을 모든 빈에 일반화하지는 않습니다.
1. 문제 모델
일반적인 순환 의존성:
A -> B -> Repository -> A
의존성 주입이 빈의 초기화가 끝나기 전에 이루어지면 Spring은 3단계 캐시를 통해 참조를 조기에 노출할 수 있습니다. 이후 빈을 초기화할 때 BeanPostProcessor가 프록시를 생성하는 과정이 거치면서 다음과 같은 불일치가 발생할 수 있습니다:
Early Reference != Final Bean
2. 3단계 캐시로 할 수 있는 일
3단계 캐시의 ObjectFactory는 조기 참조를 제공할 수 있습니다. SmartInstantiationAwareBeanPostProcessor를 구현하는 후처리기는 getEarlyBeanReference를 통해 조기 프록시 생성에 참여할 수 있습니다.
AbstractAutoProxyCreator는 조기 프록시 참조를 기록하여 동일한 자동 프록시가 초기화 후에 중복 생성되지 않도록 합니다. 다만 모든 BeanPostProcessor가 조기 참조 처리에 참여하는 것은 아닙니다.
일반적인 후처리기가 postProcessAfterInitialization 단계에서만 빈을 래핑한다면 조기 참조가 최종 객체와 다른 객체일 수 있습니다. 실제로 차이가 발생하는지는 Spring 버전, 후처리 순서, 프록시 유형에 따라 달라집니다.
3. Spring Boot 3에서의 처리 권장 사항
Spring Boot 2.6부터 순환 의존성의 기본 처리가 더 엄격해졌습니다. Boot 3 프로젝트에서는 우선해서 순환 의존성을 제거해야 하며, spring.main.allow-circular-references=true를 기본 해결책으로 삼지 마십시오.
권장 순서:
- 서비스와 Repository의 책임을 분리합니다.
- 이벤트 또는 인터페이스를 활용해 의존 방향을 역전합니다.
- 지연 생성이 꼭 필요한 의존성에는 @Lazy를 사용하고 라이프사이클을 검토합니다.
- 트랜잭션, 캐시, 비동기 처리, 예외 변환 및 사용자 정의 후처리기가 프록시를 생성하는지 확인합니다.
4. 문제 해결 방법
실제로 사용 중인 Spring Framework 버전을 기록합니다. 빈의 프록시 유형과 Advisor를 확인합니다. getEarlyBeanReference와 postProcessAfterInitialization을 각각 추적합니다. 여러 후처리기가 빈을 중복으로 래핑하는지 확인합니다. allowCircularReferences와 BeanDefinition 순서를 점검합니다.
3단계 캐시는 “모든 순환 의존성을 해결할 수 있다”는 보장이 아닙니다. 컨테이너가 특정 라이프사이클 단계에서 조기 참조를 제공하는 메커니즘일 뿐이며, 결국에는 명확하고 유지 관리 가능한 의존성 그래프가 필요합니다.