Как решать циклическую зависимость классов при инжекции зависимостей
Что проверяют
Понимаете ли вы, почему цикл ломает создание бинов и что его правильнее устранять в дизайне, а не прятать настройкой. Следом обычно спрашивают, почему с внедрением через конструктор цикл падает сразу, а с полями раньше «работал».
Как отвечать
Цикл это A зависит от B, а B от A (или длиннее: A, B, C и снова A). С внедрением через конструктор контейнеру нечего создать первым, и Spring падает с BeanCurrentlyInCreationException. С внедрением через поле или сеттер Spring умеет подсунуть ещё не до конца инициализированный экземпляр через ранние ссылки, поэтому такие циклы годами жили незаметно. Начиная со Spring Boot 2.6 циклы запрещены по умолчанию и приложение не стартует, а spring.main.allow-circular-references=true возвращает старое поведение.
Способы по убыванию предпочтительности:
- пересобрать ответственность: чаще всего цикл значит, что общий кусок логики нужно вынести в третий сервис C, от которого зависят оба;
- события вместо прямого вызова: A публикует событие через
ApplicationEventPublisher, B слушает его через@EventListener, и обратной ссылки не остаётся; - отложенное получение:
@Lazyна параметре конструктора, тогда Spring внедряет прокси и достаёт настоящий бин при первом вызове; илиObjectProvider<B>иgetObject()в момент использования; - сеттер вместо конструктора для одной из связей: работает, только если циклы разрешены, и это тот же обход.
Флаг и @Lazy лечат симптом: зависимость никуда не делась, её просто стало труднее увидеть. На собеседовании стоит сказать это прямо.
Где спотыкаются
Предлагают «ввести интерфейс». Интерфейс развязывает классы на уровне компиляции, но контейнеру по-прежнему нужен бин A, чтобы создать B, и наоборот. Цикл в графе бинов остаётся.
Первым делом включают allow-circular-references. Интервьюер слышит в этом «я не стал разбираться с дизайном».
Путают циклы бинов с циклами между пакетами или модулями сборки. Там тоже бывают циклы, но это другая задача: её ловят тестами архитектуры вроде ArchUnit, а не настройкой контейнера.