Как разрешить циклическую зависимость (circular dependency) при внедрении в Spring?

«Как разрешить циклическую зависимость (circular dependency) при внедрении в Spring?» — вопрос из категории Spring, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Циклические зависимости (A → B → A) — это антипаттерн, сигнализирующий о проблемах проектирования. Временные решения в Spring:

1. Использование @Lazy Инжектируйте одну из зависимостей лениво, разрывая цикл на этапе инициализации.

@Service
public class ServiceA {
    private final ServiceB serviceB;

    public ServiceA(@Lazy ServiceB serviceB) { // Lazy-прокси для B
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    private final ServiceA serviceA; // Внедряется реальный бин A

    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

2. Инжекция через сеттер или поле Вместо инжекции через конструктор (которая вызывает ошибку на этапе подъёма контекста), используйте @Autowired на поле или сеттер-методе.

3. Рефакторинг архитектуры (рекомендуется)

  • Вынесите общую логику в третий сервис (паттерн Mediator).
  • Используйте интерфейсы и инжектируйте их, возможно, с помощью @Lazy.
  • Примените принцип инверсии зависимостей (DIP) — сделайте так, чтобы оба класса зависели от абстракции, а не друг от друга.

Важно: Spring Boot ≥ 2.6 по умолчанию запрещает циклические зависимости. Для временного обхода можно установить spring.main.allow-circular-references=true, но это плохая практика. Лучшее решение — рефакторинг.