DEV Community

Cover image for Проклятие Циклической Зависимости
Olga Lugacheva
Olga Lugacheva

Posted on • Updated on

Проклятие Циклической Зависимости

В одном королевстве бинов, управляемых великим Контейнером Spring, неожиданно возникло страшное проклятие — цикл тьмы, известный как Циклическая Зависимость. Никто не знал, откуда пришло это зло, но каждый бин, затянутый в круг, оказывался в бесконечной петле, разрушая всю деревню.

В этом мрачном рассказе скрывается распространённая проблема, с которой сталкиваются многие разработчики Spring — циклическая зависимость между бинами. Давайте разберём, как это происходит и что можно сделать, чтобы избежать этой ловушки.

Что Такое Циклическая Зависимость?

Наш Spring Container окружён миражами зависимостей. Внутри замка бины ждут своей очереди на инициализацию.

Spring Container
В мире Spring, когда два или более бина зависят друг от друга, образуется цикл. Это означает, что бин A зависит от бина B, а бин B — от бина A. Контейнер Spring не может создать эти бины, потому что для создания каждого из них требуется, чтобы другой бин уже существовал. Этот порочный круг приводит к ошибкам, которые могут парализовать приложение.

Рассмотрим следующий пример циклической зависимости:

@Component
public class BeanA {
    private final BeanB beanB;

    @Autowired
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}

Enter fullscreen mode Exit fullscreen mode
@Component
public class BeanB {
    private final BeanA beanA;

    @Autowired
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}
Enter fullscreen mode Exit fullscreen mode

При запуске приложения Spring столкнётся с проблемой. Контейнер начнёт создание BeanA, но для этого потребуется BeanB. Однако BeanB также зависит от BeanA, что приводит к бесконечному циклу.

cycle dependency

Как Spring Реагирует на Циклические Зависимости?

Increation Ex
Когда Spring сталкивается с циклом, он выдаёт ошибку:

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
Enter fullscreen mode Exit fullscreen mode

Эта ошибка говорит о том, что Spring не может завершить создание бинов, поскольку один из них ещё не был завершён. В результате приложение не запускается.

Способы Разрыва Циклической Зависимости
Чтобы освободить бины от проклятия циклической зависимости, есть несколько приёмов, которые помогут разорвать цикл и вернуть порядок в деревню бинов.

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

Аннотация @Lazy позволяет отложить создание бина до момента, когда он действительно понадобится. Таким образом, если мы пометим зависимость как @Lazy, то Spring не попытается сразу создать бин, позволяя другим бинам завершить инициализацию.

Применим @Lazy к одному из бинов:

@Component
public class BeanA {
    private final BeanB beanB;

    @Autowired
    public BeanA(@Lazy BeanB beanB) {
        this.beanB = beanB;
    }
}

Enter fullscreen mode Exit fullscreen mode

Теперь при запуске Spring не будет сразу инициализировать BeanB, а сделает это только тогда, когда он действительно понадобится. Это позволяет избежать циклической зависимости.

Способ 2: Использование @Autowired над Методом (Setter Injection)

Иногда можно избавиться от цикла, применяя метод для внедрения зависимости вместо конструктора. Spring создаёт бины с использованием сеттеров после инициализации основного объекта, что позволяет разорвать круг зависимостей.

@Component
public class BeanA {
    private BeanB beanB;

    @Autowired
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

Enter fullscreen mode Exit fullscreen mode

С помощью этого подхода BeanA создаётся первым, а зависимость от BeanB устанавливается позже, после завершения инициализации основного объекта.

Способ 3: Пересмотр Архитектуры и Разделение Зависимостей

Common service

Циклические зависимости часто являются признаком проблемной архитектуры. Если две сущности так сильно зависят друг от друга, возможно, их стоит объединить или, наоборот, разделить на более мелкие компоненты. Например, вместо того чтобы создавать две зависимости друг от друга, можно выделить общий интерфейс или сервис, который будет использоваться обеими сущностями.

@Component
public class CommonService {
    public void performAction() {
        // общая логика
    }
}

@Component
public class BeanA {
    private final CommonService commonService;

    @Autowired
    public BeanA(CommonService commonService) {
        this.commonService = commonService;
    }
}

@Component
public class BeanB {
    private final CommonService commonService;

    @Autowired
    public BeanB(CommonService commonService) {
        this.commonService = commonService;
    }
}

Enter fullscreen mode Exit fullscreen mode

Заключение

Проклятие циклической зависимости может настигнуть любого, кто недостаточно внимательно проектирует зависимости в Spring. Чтобы избежать этой ловушки, необходимо следить за тем, чтобы бины не зависели друг от друга напрямую, и использовать такие инструменты, как @lazy, инъекции через сеттеры и разделение ответственности. Соблюдая эти принципы, можно разорвать цикл и освободить контейнер Spring от зловещего проклятия, позволяя всем бинам жить в мире и согласии.

Top comments (0)