Что такое циклическая зависимость бинов в Spring
Что проверяют
Понимаете ли вы, как контейнер строит граф бинов, и отличаете ли проблему проектирования от технического обхода. Вопрос любят, потому что кандидат либо знает про порядок создания и раннюю ссылку, либо просто вспоминает аннотацию, которая всё чинит.
Как отвечать
Цикл это когда бин A нужен для создания B, а B нужен для создания A, напрямую или через цепочку. Контейнер не может завершить ни один из них и падает с BeanCurrentlyInCreationException.
Важная деталь, ради которой вопрос и задают: всё зависит от способа внедрения. При внедрении через конструктор цикл неразрешим в принципе, объект нельзя сконструировать без готовой зависимости. При внедрении в поле или сеттер контейнер выкручивался: он клал недостроенный экземпляр в кэш ранних ссылок и подставлял его второму бину. В современных версиях Spring Boot такое поведение выключено по умолчанию и включается отдельным свойством, то есть цикл теперь ошибка старта, а не тихий костыль.
Порядок ответа такой. Сначала честная позиция: цикл это симптом размазанных обязанностей, и лечение это выделить третий бин с общей логикой либо перевернуть зависимость через интерфейс или события.
Если рефакторинг сейчас невозможен, назовите обходы: @Lazy на параметре конструктора, чтобы подставился прокси, ObjectProvider для получения зависимости в момент вызова, перенос обращения в метод с @PostConstruct.
Где спотыкаются
Говорят, что конструкторное внедрение спасает от циклов. Наоборот: оно их обнажает на старте, и в этом его ценность, но проблему оно не решает.
Ставят @Lazy на оба конца и радуются зелёному старту. Цикл никуда не делся, он переехал в рантайм и всплывёт при первом обращении или в тесте со срезом контекста.
Не видят разницы между циклом бинов и циклом классов. Компилятор взаимные ссылки классов допускает, ломается именно сборка графа контейнера, поэтому проблема воспроизводится только при поднятии контекста.