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

Разбирался ли с циклическими зависимостями

2.0 Middle🔥 161 комментариев
#SOLID и паттерны проектирования#Spring Framework

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

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

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

Разбирался ли с циклическими зависимостями?

Это вопрос про архитектуру кода и управление зависимостями. Циклические зависимости (circular dependencies) — это частая проблема, которая указывает на плохую архитектуру. Рассмотрю разные аспекты.

Что такое циклическая зависимость?

Это ситуация, когда:

  • Класс A зависит от класса B
  • Класс B зависит от класса C
  • Класс C зависит обратно на класс A

Структура циклична: A → B → C → A

// ❌ Пример циклической зависимости

// Класс User зависит от UserService
public class User {
    private UserService userService;  // Зависит от UserService
    
    public void updateProfile() {
        userService.update(this);
    }
}

// Класс UserService зависит от User... ждите, он уже от User зависит выше
// Но если UserService вернёт User, а User опять вызовет UserService...
// Получится циклическая зависимость!

Виды циклических зависимостей

1. Циклические зависимости в compile-time (самые проблемные)

// UserRepository.java
public interface UserRepository {
    User findById(Long id);
}

// UserService.java
public class UserService {
    @Autowired
    private UserRepository repository;  // Зависит от UserRepository
}

// UserRepositoryImpl.java
public class UserRepositoryImpl implements UserRepository {
    @Autowired
    private UserService service;  // Зависит от UserService!
    
    public User findById(Long id) {
        // Используем service
        return service.enrichUser(new User(id));
    }
}

// ❌ ЦИКЛИЧЕСКАЯ ЗАВИСИМОСТЬ: UserRepository → UserService → UserRepository

2. Циклические зависимости в runtime (Spring beans)

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;  // OrderService → PaymentService
    
    public void createOrder(Order order) {
        paymentService.processPayment(order);
    }
}

@Service
public class PaymentService {
    @Autowired
    private OrderService orderService;  // PaymentService → OrderService!
    
    public void processPayment(Order order) {
        if (isPaid(order)) {
            orderService.completeOrder(order);
        }
    }
}

// ❌ ЦИКЛИЧЕСКАЯ ЗАВИСИМОСТЬ В RUNTIME

Почему циклические зависимости — проблема?

1. Spring не может создать beans

Spring пытается создать OrderService:
  → Нужен PaymentService
    → Нужен OrderService... но его уже создаём!
    → DEADLOCK! BeanCurrentlyInCreationException

Ошибка при запуске приложения:

BeanCurrentlyInCreationException: 
Error creating bean with name 'orderService': 
Requested bean is currently in creation: 
Is there an unresolvable circular reference?

2. Сложно тестировать

// Как написать unit-тест?
@Test
void testCreateOrder() {
    // Нужно создать OrderService с PaymentService
    PaymentService paymentService = mock(PaymentService.class);
    OrderService orderService = new OrderService(paymentService);
    
    // Но PaymentService нужен OrderService!
    // Не можешь создать один без другого
}

3. Указывает на плохую архитектуру

Циклические зависимости — это smetl code. Обычно это значит:

  • Классы имеют слишком много ответственности (нарушают SRP)
  • Зависимости плохо разделены
  • Нужен рефакторинг

Как я разбирался с циклическими зависимостями

Пример из реального проекта:

// ДО: циклическая зависимость

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;
    @Autowired
    private NotificationService notificationService;
    
    public void createOrder(Order order) {
        paymentService.processPayment(order);
        notificationService.notifyUser(order);
    }
}

@Service
public class PaymentService {
    @Autowired
    private OrderService orderService;  // Циклическая зависимость!
    
    public void processPayment(Order order) {
        if (isPaid(order)) {
            orderService.updateOrderStatus(order);  // Вызывает OrderService
        }
    }
}

// ❌ Ошибка: BeanCurrentlyInCreationException

Решение 1: Внедрить промежуточный сервис (EventPublisher)

// Новый сервис, который медиирует между ними
@Service
public class OrderEventPublisher {
    // Только публикует события, не зависит от конкретных сервисов
}

// OrderService НЕ зависит от PaymentService напрямую
@Service
public class OrderService {
    @Autowired
    private OrderEventPublisher eventPublisher;
    
    public void createOrder(Order order) {
        eventPublisher.publishOrderCreated(order);
    }
}

// PaymentService слушает события
@Service
public class PaymentService {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        Order order = event.getOrder();
        processPayment(order);
    }
}

// ✅ Циклическая зависимость разорвана!
// OrderService → EventPublisher ← PaymentService (одностороннее)

Решение 2: Разделить на отдельные интерфейсы

// Создаём тонкие интерфейсы, а не монолитные сервисы

public interface PaymentProcessor {
    void processPayment(Order order);
}

public interface OrderRepository {
    void save(Order order);
}

@Service
public class OrderService {
    private final PaymentProcessor paymentProcessor;
    private final OrderRepository orderRepository;
    
    public OrderService(PaymentProcessor paymentProcessor, OrderRepository orderRepository) {
        this.paymentProcessor = paymentProcessor;
        this.orderRepository = orderRepository;
    }
    
    public void createOrder(Order order) {
        paymentProcessor.processPayment(order);
        orderRepository.save(order);
    }
}

// PaymentService реализует только PaymentProcessor
// Не зависит от OrderService, не может вызвать его
@Service
public class PaymentService implements PaymentProcessor {
    @Override
    public void processPayment(Order order) {
        // Процесс платежа
    }
}

// ✅ Зависимости однонаправленные!
// OrderService → PaymentProcessor (интерфейс, а не конкретный сервис)

Решение 3: Использовать ObjectProvider для lazy loading

// Если циклическая зависимость неизбежна, используй ObjectProvider

@Service
public class OrderService {
    private final PaymentService paymentService;
    private final ObjectProvider<NotificationService> notificationService;
    
    public OrderService(
        PaymentService paymentService,
        ObjectProvider<NotificationService> notificationService) {
        this.paymentService = paymentService;
        this.notificationService = notificationService;
    }
    
    public void createOrder(Order order) {
        paymentService.processPayment(order);
        // notificationService загружается лениво, когда нужна
        notificationService.ifAvailable(ns -> ns.notifyUser(order));
    }
}

@Service
public class PaymentService {
    private final ObjectProvider<OrderService> orderService;
    
    public PaymentService(ObjectProvider<OrderService> orderService) {
        this.orderService = orderService;
    }
    
    public void processPayment(Order order) {
        // ...
        // orderService загружается лениво
        orderService.ifAvailable(os -> os.updateOrderStatus(order));
    }
}

// ✅ Spring может создать beans благодаря lazy loading

Решение 4: Использовать @Lazy

@Service
public class OrderService {
    private final PaymentService paymentService;
    
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }
}

@Service
public class PaymentService {
    private final OrderService orderService;
    
    public PaymentService(@Lazy OrderService orderService) {
        this.orderService = orderService;  // Прокси, не реальный объект
    }
}

// ✅ @Lazy создаёт прокси, который загружается позже

Как я обнаруживаю и исправляю циклические зависимости

Шаг 1: Обнаружение

Ошибка при запуске Spring:
BeanCurrentlyInCreationException: 
Error creating bean with name 'orderService'

Шаг 2: Анализ

Отслеживаю, где циклус:
OrderService → PaymentService → OrderService

Шаг 3: Выбор решения

1. Event-based communication (лучший вариант)
   Заменю прямые вызовы на события

2. Interface segregation (хороший вариант)
   Разделю на тонкие интерфейсы

3. Lazy loading (худший вариант)
   Использую только если переделать нельзя

Шаг 4: Рефакторинг

Переделаю архитектуру, чтобы зависимости были однонаправленными

Best Practice для избежания циклических зависимостей

1. Следуй Dependency Inversion Principle

// ❌ Плохо — зависит от конкретного класса
public class OrderService {
    private PaymentService paymentService;  // Конкретный класс
}

// ✅ Хорошо — зависит от интерфейса
public class OrderService {
    private PaymentProcessor paymentProcessor;  // Интерфейс
}

2. Используй Event-driven архитектуру вместо direct calls

// ❌ Плохо
orderService.createOrder();
paymentService.processPayment();  // Direct call

// ✅ Хорошо
eventBus.publish(new OrderCreatedEvent(order));
// PaymentService слушает это событие

3. Разделяй классы по слоям (layered architecture)

presentation ← application ← domain ← infrastructure

Зависимости только ВНИЗ по слоям, никогда ВВЕРХ!
Это гарантирует, что нет циклов.

4. Используй Constructor Injection для явности

// Constructor injection показывает все зависимости
public OrderService(PaymentService payment, OrderRepository repo) {
    // Если есть цикл, сразу видно при компиляции
}

Что я скажу на собеседовании

"Да, разбирался с циклическими зависимостями.

У нас была архитектура, где OrderService вызывал PaymentService,
а PaymentService вызывал обратно OrderService.
Это вызывало BeanCurrentlyInCreationException при запуске Spring.

Я решил эту проблему, внедрив Event-driven архитектуру.
Вместо прямых вызовов, мы публикуем события:
- OrderService публикует OrderCreatedEvent
- PaymentService слушает это событие и обрабатывает платёж

Это разорвало циклическую зависимость и сделало код более гибким.
Теперь если нужно добавить NotificationService, это просто ещё
одна @EventListener, без изменения существующего кода.

Вообще, циклические зависимости указывают на плохую архитектуру,
и обычно нужен рефакторинг, а не хак с @Lazy или ObjectProvider."

Вывод

✅ Циклические зависимости — это red flag для архитектуры ✅ Решение: Event-driven, Interface segregation, Layered architecture ✅ Избегай хаков (@Lazy, ObjectProvider) — переделай архитектуру ✅ Constructor injection помогает обнаружить циклы рано ✅ Если видишь цикл — это возможность рефакторить и улучшить код