Как решить циклическую зависимость через сеттеры в Spring
Что проверяют
Понимаете ли вы, почему сеттерная инъекция вообще разрывает цикл, и знаете ли, что в современном Spring Boot такой цикл по умолчанию всё равно не стартует. Следом почти всегда спрашивают: а как правильно, если не сеттерами?
Как отвечать
Начните с механики. При внедрении через конструктор бин A нельзя создать, пока нет B, а B ждёт A: контекст падает с BeanCurrentlyInCreationException. При внедрении через сеттер или поле Spring сначала создаёт экземпляр A, кладёт раннюю ссылку на него в кеш синглтонов и только потом заполняет свойства. Когда B просит A, он получает эту раннюю, ещё не настроенную ссылку.
Дальше важные оговорки:
- только синглтоны: для бинов со scope
prototypeранней ссылки нет, цикл не разрешится; - Spring Boot 2.6+: свойство
spring.main.allow-circular-referencesпо умолчаниюfalse, и даже сеттерный цикл роняет старт. Включить обратно можно, но это признание, что дизайн сломан; - прокси: если постпроцессор оборачивает бин поздно, B получит ссылку на «сырой» объект. Типичный пример
@Async, который на цикле роняет старт; @Lazyна параметре конструктора: Spring подставляет ленивый прокси, настоящий бин достаётся при первом вызове;ObjectProvider<B>: зависимость запрашивается в момент использования, а не при создании.
Главное, что стоит сказать: сеттеры лечат симптом. Правильное решение это разорвать цикл в дизайне: вынести общую логику в третий сервис или перейти на события через ApplicationEventPublisher.
Где спотыкаются
Советуют сеттеры как основной способ и не знают, что в Spring Boot начиная с 2.6 проект с таким циклом без флага просто не запустится.
Путают сеттерную инъекцию с @PostConstruct. Этот метод вызывается уже после внедрения зависимостей и цикл никак не разрывает.
Не замечают рекурсию в самих вызовах: если A.doSomething() зовёт B, а тот снова A, контекст поднимется, но первый же запрос упадёт со StackOverflowError.