Почему Spring не любит циклические зависимости?
Представим вполне реалистичную ситуацию:
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
И:
@Service
public class PaymentService {
private final OrderService orderService;
public PaymentService(OrderService orderService) {
this.orderService = orderService;
}
}
Получается:
OrderService
↓
PaymentService
↓
OrderService
↓
PaymentService
↓
...
Spring начинает создавать OrderService.
Для него нужен PaymentService.
Начинает создавать PaymentService.
Для него нужен OrderService.
А OrderService ещё не создан.
💥 Получаем циклическую зависимость.
И здесь есть важный момент. Почему constructor injection — это хорошо?
Потому что такая проблема становится очевидной сразу.
Объект невозможно создать, пока не готовы все его обязательные зависимости.
При field injection ситуация исторически могла выглядеть менее очевидно: Spring сначала создавал объект, а зависимости внедрял позже.
Но главное даже не ошибка Spring.
Циклическая зависимость часто говорит о проблеме в дизайне.
Если:
OrderService → PaymentService
PaymentService → OrderService
то возникает вопрос:
А почему эти два сервиса вообще знают друг о друге
?
Возможно, ответственность стоит разделить:
OrderService
↓
PaymentService
↓
PaymentRepository
или вынести общую бизнес-логику в отдельный компонент:
OrderService
↓
PaymentService
↓
PaymentProcessor
↑
OrderService
То есть Spring здесь не столько «мешает разработчику», сколько показывает:
у нас два компонента слишком сильно связаны между собой.
Поэтому если спрашивают:
Как исправить циклическую зависимость?
Не спешите отвечать: Поставить @Lazy.
Это может убрать проблему создания Bean, но не исправляет архитектурную связь.
Сначала стоит спросить:
«Почему вообще A должен зависеть от B, а B от A?»
Иногда лучший способ исправить циклическую зависимость — не искать правильную аннотацию, а изменить дизайн.