← Назад к вопросам
Сталкивался ли с циклическими зависимостями бинов в Spring
1.8 Middle🔥 221 комментариев
#Docker, Kubernetes и DevOps
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Циклические зависимости в Spring: проблема и решения
Да, я сталкивался с циклическими зависимостями между бинами в Spring и это была полезная ошибка для глубокого понимания того, как работает контейнер зависимостей.
Пример циклической зависимости
@Component
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
this.serviceA = serviceA;
}
}
Эта конфигурация приведёт к ошибке:
BeanCurrentlyInCreationException: Error creating bean with name serviceA:
Requested bean is currently in creation: Is there an unresolvable circular reference?
Как Spring создаёт бины
Spring использует трёхуровневую систему кэширования для обработки циклических зависимостей:
- singletonObjects — готовые бины (level 1)
- earlySingletonObjects — бины в процессе создания (level 2)
- singletonFactories — фабрики для создания бинов (level 3)
// Упрощённо, как работает механизм:
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
private final Map<String, Object> earlySingletonObjects = new HashMap<>();
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
Это позволяет обработать циклические зависимости при инъекции через поле (field injection).
Способ 1: Инъекция через поле (Field Injection)
@Component
public class ServiceA {
@Autowired
private ServiceB serviceB;
public void doWork() {
serviceB.process();
}
}
@Component
public class ServiceB {
@Autowired
private ServiceA serviceA;
public void process() {
// ...
}
}
Это работает благодаря earlySingletonObjects, но это не лучшая практика, так как скрывает зависимости.
Способ 2: Использование ObjectProvider (ПРАВИЛЬНЫЙ ПОДХОД)
@Component
public class ServiceA {
private final ObjectProvider<ServiceB> serviceBProvider;
public ServiceA(ObjectProvider<ServiceB> serviceBProvider) {
this.serviceBProvider = serviceBProvider;
}
public void doWork() {
ServiceB serviceB = serviceBProvider.getIfAvailable();
if (serviceB != null) {
serviceB.process();
}
}
}
@Component
public class ServiceB {
private final ObjectProvider<ServiceA> serviceAProvider;
public ServiceB(ObjectProvider<ServiceA> serviceAProvider) {
this.serviceAProvider = serviceAProvider;
}
public void process() {
// ...
}
}
Способ 3: Использование @Lazy
@Component
public class ServiceA {
private final ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Component
public class ServiceB {
private final ServiceA serviceA;
public ServiceB(@Lazy ServiceA serviceA) {
this.serviceA = serviceA;
}
}
Способ 4: Рефакторинг архитектуры
Делим ответственность на новый сервис:
@Component
public class ServiceA {
private final Coordinator coordinator;
public ServiceA(Coordinator coordinator) {
this.coordinator = coordinator;
}
}
@Component
public class ServiceB {
private final Coordinator coordinator;
public ServiceB(Coordinator coordinator) {
this.coordinator = coordinator;
}
}
@Component
public class Coordinator {
private final ServiceA serviceA;
private final ServiceB serviceB;
public Coordinator(ServiceA serviceA, ServiceB serviceB) {
this.serviceA = serviceA;
this.serviceB = serviceB;
}
}
Решение, которое я применял
Мой практический подход:
- Первое — пересмотреть дизайн. Циклические зависимости обычно признак плохой архитектуры.
- Второе — использовать ObjectProvider для ленивого разрешения зависимостей.
- Третье — если нужно, применить паттерн Event-Driven Architecture.
Пример с Events
@Component
public class ServiceA {
private final ApplicationEventPublisher eventPublisher;
public ServiceA(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void doWork() {
eventPublisher.publishEvent(new WorkEvent(this));
}
}
@Component
public class ServiceB {
@EventListener
public void onWorkEvent(WorkEvent event) {
// Реагируем на событие без прямой зависимости
}
}
Ключевые выводы
- Избегайте циклических зависимостей на уровне архитектуры
- Используйте ObjectProvider вместо прямой инъекции в сложных случаях
- @Lazy помогает, но замаскирует проблему
- Event-driven архитектура часто лучший способ развязать компоненты