← Назад к вопросам

Какие знаешь способы решения циклической зависимости Bean в Spring?

2.8 Senior🔥 81 комментариев
#Spring Framework

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение циклической зависимости Bean в Spring

Циклическая зависимость (Circular Dependency) возникает, когда Bean A зависит от Bean B, а Bean B зависит от Bean A. Это приводит к BeanCurrentlyInCreationException при запуске приложения.

Проблема: BeanCurrentlyInCreationException

@Component
public class UserService {
    @Autowired
    private OrderService orderService; // Зависимость от OrderService
    
    public void createUser(User user) {
        userRepository.save(user);
        // orderService используется где-то
    }
}

@Component
public class OrderService {
    @Autowired
    private UserService userService; // Циклическая зависимость!
    
    public void createOrder(Order order) {
        order.setUser(userService.getCurrentUser());
        orderRepository.save(order);
    }
}

/* Ошибка при старте:
org.springframework.beans.factory.BeanCurrentlyInCreationException:
 Error creating bean with name userService:
 Bean with name userService has been injected into other beans
 [orderService] in its raw version as part of a circular reference,
 but has eventually been wrapped.
*/

Зачем это происходит:

UserService создание:
  → Нужен OrderService
    → Нужен UserService (которая ещё не создана!)
      → Infinite loop

Способ 1: Использование @Lazy

Отложить инъекцию зависимости до момента её использования:

@Component
public class UserService {
    @Autowired
    @Lazy  // Не создавать сразу, а при первом использовании
    private OrderService orderService;
    
    public void createUser(User user) {
        userRepository.save(user);
        orderService.checkOrders(user.getId()); // Теперь инъектируется
    }
}

@Component
public class OrderService {
    @Autowired
    private UserService userService;
    
    public void createOrder(Order order) {
        order.setUser(userService.getCurrentUser());
        orderRepository.save(order);
    }
}

Как это работает:

UserService создание:
  → Нужен OrderService
    → Нужен UserService (уже создана)
      → OrderService создана с Lazy proxy для UserService
  → UserService создана

При первом вызове orderService:
  → Spring инстанцирует реальный OrderService

Преимущества:

  • Просто, одна аннотация
  • Не требует изменения логики
  • Хорошо для редких зависимостей

Недостатки:

  • Может скрыть дизайн-проблемы
  • Зависимость инъектируется как Proxy

Способ 2: Constructor injection + Setter

Импользовать конструктор для основной зависимости, а сеттер для циклической:

@Component
public class UserService {
    private OrderService orderService;
    
    // Основная зависимость в конструкторе
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    // Циклическая зависимость через сеттер
    @Autowired
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
}

@Component
public class OrderService {
    @Autowired
    private UserService userService; // Конструктор не требует её
    
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }
}

Как это работает:

1. UserService создание (конструктор без OrderService)
   → Нужна только UserRepository
2. OrderService создание
   → Нужна OrderRepository и UserService
   → UserService уже создана → OK
3. Spring вызывает setOrderService() на UserService
   → OrderService инъектируется

Преимущества:

  • Явно показывает, какие зависимости критичные (конструктор)
  • Правильный дизайн

Недостатки:

  • Требует изменения кода
  • Может привести к NullPointerException если забыть инициализировать

Способ 3: ObjectFactory

Использовать ObjectFactory для ленивого создания:

@Component
public class UserService {
    @Autowired
    private ObjectFactory<OrderService> orderServiceFactory;
    
    public void createUser(User user) {
        userRepository.save(user);
        // Получить при использовании
        OrderService orderService = orderServiceFactory.getObject();
        orderService.checkOrders(user.getId());
    }
}

@Component
public class OrderService {
    @Autowired
    private UserService userService;
    
    public void createOrder(Order order) {
        order.setUser(userService.getCurrentUser());
        orderRepository.save(order);
    }
}

Преимущества:

  • Явное ленивое создание
  • Контролируется в коде
  • Каждый getObject() возвращает новый экземпляр (если prototype)

Недостатки:

  • Более verbose код
  • ObjectFactory видна в бизнес-логике

Способ 4: Provider interface

Аналог ObjectFactory, но через javax.inject.Provider:

@Component
public class UserService {
    @Autowired
    private Provider<OrderService> orderServiceProvider;
    
    public void createUser(User user) {
        userRepository.save(user);
        OrderService orderService = orderServiceProvider.get();
        orderService.checkOrders(user.getId());
    }
}

@Component
public class OrderService {
    @Autowired
    private UserService userService;
    
    public void createOrder(Order order) {
        order.setUser(userService.getCurrentUser());
        orderRepository.save(order);
    }
}

Отличие от ObjectFactory:

  • Используется javax.inject.Provider (стандартный интерфейс)
  • Семантически то же самое

Способ 5: Event-driven communication

Вместо прямой инъекции использовать события:

@Component
public class UserService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public void createUser(User user) {
        userRepository.save(user);
        // Вместо вызова OrderService напрямую
        eventPublisher.publishEvent(new UserCreatedEvent(user.getId()));
    }
}

@Component
public class OrderService {
    @EventListener
    public void onUserCreated(UserCreatedEvent event) {
        // Слушаем событие, не требуя инъекции UserService
        User user = userService.getUser(event.getUserId());
        // Обработка...
    }
}

Преимущества:

  • Полностью развязывает компоненты
  • Легко расширять (много слушателей)
  • Асинхронное выполнение возможно

Недостатки:

  • Более сложная отладка
  • Нужно следить за порядком обработки событий

Способ 6: Refactoring: Extracting interface

Перенести общую логику в отдельный сервис:

// Было:
@Component
public class UserService {
    @Autowired
    private OrderService orderService; // Циклическая зависимость
}

@Component
public class OrderService {
    @Autowired
    private UserService userService; // Циклическая зависимость
}

// Решение: Отделить общую логику
@Component
public class UserService {
    @Autowired
    private UserOrderFacade userOrderFacade; // Используемая логика
}

@Component
public class OrderService {
    @Autowired
    private UserOrderFacade userOrderFacade; // Та же логика
}

@Component
public class UserOrderFacade {
    // Общая логика, нет циклических зависимостей
}

Это лучший дизайн-паттерн!

Способ 7: Setters и инициализация

Использовать InitializingBean или @PostConstruct:

@Component
public class UserService implements InitializingBean {
    @Autowired
    private OrderService orderService;
    
    @Override
    public void afterPropertiesSet() throws Exception {
        // Инициализация после того, как все зависимости инъектированы
        initializeUserOrderDependencies();
    }
    
    private void initializeUserOrderDependencies() {
        // Здесь оба сервиса полностью инициализированы
    }
}

@Component
public class OrderService {
    @Autowired
    private UserService userService;
    
    @PostConstruct
    public void init() {
        // Аналог afterPropertiesSet()
    }
}

Сравнение способов

СпособПростотаPerformanceDesignРекомендуется
@LazyОтличноХорошоПлохоДля быстрого фикса
Constructor + SetterХорошоОтличноХорошоКогда возможно
ObjectFactoryХорошоХорошоНейтральноДля ленивых зависимостей
Event-drivenСложноОтличноОтличноДля large apps
RefactoringХорошоОтличноОтличноЛУЧШЕ всех

Best practice решение

Рекомендуемый порядок действий:

  1. Сначала refactor — переделай архитектуру:
// Плохо: циклические зависимости
UserService → OrderService → UserService

// Хорошо: слои
Controller → UserService → UserRepository
Controller → OrderService → OrderRepository
Controller → UserOrderFacade → (оба сервиса)
  1. Если refactor невозможен:
// Используй Constructor + Setter
@Component
public class UserService {
    public UserService(UserRepository repo) { } // Конструктор
    
    @Autowired
    public void setOrderService(OrderService svc) { } // Сеттер
}
  1. Как временное решение:
// @Lazy для быстрого фикса
@Autowired
@Lazy
private OrderService orderService;

Диагностирование циклической зависимости

# В логах Spring ищи такие сообщения:
BeanCurrentlyInCreationException: Error creating bean
BeanInstantiationException: Failed to instantiate
UnsatisfiedDependencyException: Error creating bean

# Stack trace укажет на циклу:
...UserService -> OrderService -> UserService -> OrderService -> UserService...

Заключение

Циклические зависимости — признак дизайн-проблемы. Правильное решение:

  1. Refactor (лучшее решение) — переделать архитектуру
  2. Constructor + Setter — если refactor требует больших изменений
  3. @Lazy — как временное решение
  4. Event-driven — для микросервисов / large applications

Никогда не игнорируй циклические зависимости — это признак плохого дизайна!

Какие знаешь способы решения циклической зависимости Bean в Spring? | PrepBro