Можно ли поднять Spring контекст с циклической зависимостью
Что проверяют
Понимаете ли вы, как контейнер собирает синглтоны, и отличаете ли «технически поднимется» от «так делать не стоит». Хороший ответ начинается со встречного уточнения: цикл через конструкторы или через поля.
Как отвечать
Цикл через конструкторы неразрешим принципиально: чтобы вызвать конструктор первого бина, нужен готовый второй, а ему нужен первый. Контейнер это обнаруживает и падает с BeanCurrentlyInCreationException.
С внедрением через поле или сеттер контейнер раньше выкручивался: он кладёт в кэш раннюю ссылку на ещё не дособранный объект, и сосед получает её. В актуальных версиях Spring Boot такое поведение выключено по умолчанию, приложение падает на старте с подробным сообщением, а вернуть его можно свойством spring.main.allow-circular-references=true.
Дальше стоит перечислить обходы, сразу называя их обходами:
@Lazyна одной из точек внедрения: подставляется прокси, настоящий бин достаётся при первом обращении;ObjectProviderили получение бина из контекста в момент вызова, а не при старте;- событие вместо прямого вызова соседа;
- вынести общую часть в третий бин.
Сильный ответ заканчивается тем, что цикл это симптом размытых обязанностей, и правильное решение обычно последнее.
Где спотыкаются
Отвечают «поставлю @Lazy» и не могут объяснить, что именно внедряется. Внедряется прокси, цикл никуда не делся, он лишь отложен до первого обращения, а для класса без интерфейса понадобится наследник, то есть класс не может быть финальным.
Второе: считают, что сеттер решает проблему полностью. Даже когда контейнер разрешает раннюю ссылку, сосед получает объект, у которого ещё не отработал метод инициализации, и обращение к его полям в этот момент даёт то пустую ссылку, то ошибку.
Третье: забывают про прокси вокруг бина. Если участник цикла обёрнут транзакциями или асинхронностью, ранняя ссылка указывает на сырой объект, а не на прокси, и контекст падает с жалобой именно на это.