Как Spring работает с перекрестными зависимостями через конструкторы
Что проверяют
Знаете ли вы, как контейнер Spring создаёт бины, и понимаете ли, почему цикл через конструкторы в принципе неразрешим. Следом обычно спрашивают, почему через поля такой цикл когда-то «работал» и что изменилось в Spring Boot.
Как отвечать
Начните с механики. Чтобы вызвать конструктор A(B b), Spring сначала должен получить готовый B. Если B сам требует A в конструкторе, контейнер видит, что A уже в процессе создания, и бросает BeanCurrentlyInCreationException. Spring Boot при старте выводит сообщение о цикле со схемой участвующих бинов.
Для инъекции через поля и сеттеры у Spring есть обходной путь: объект создаётся без зависимостей, ранняя ссылка на него попадает в кеш синглтонов и отдаётся второму бину до завершения инициализации. С конструктором так нельзя: объекта ещё не существует. А начиная со Spring Boot 2.6 и этот путь выключен по умолчанию (spring.main.allow-circular-references=false), приложение с циклом просто не стартует.
Способы разорвать цикл:
@Lazyна параметре конструктора: вместо настоящегоAвBприходит прокси, который найдёт бин при первом вызове метода. СамAсоздаётся как обычно;ObjectProvider<A>: зависимость достаётся явно черезgetObject()в момент использования;- события:
ApplicationEventPublisherвместо прямого вызова, если одна сторона только уведомляет другую; - рефакторинг: чаще всего правильный ответ. Цикл говорит о том, что у двух классов есть общая ответственность, её выносят в третий бин, от которого зависят оба.
Хорошо прозвучит оговорка, что @Lazy и провайдеры это обход, а не лечение.
Где спотыкаются
Утверждают, что Spring сам разрулит цикл через кеш ранних ссылок. Для конструкторов этот механизм не работает, и allow-circular-references=true конструкторный цикл не спасает.
Советуют @Primary или @Qualifier. Они выбирают между несколькими кандидатами одного типа и к циклам отношения не имеют.
Вызывают ленивую зависимость прямо в конструкторе или в @PostConstruct. Прокси при первом обращении идёт за бином, который ещё создаётся, и цикл возвращается той же ошибкой.