Круговая зависимость в Spring


100

Как Spring решает эту проблему: bean-компонент A зависит от bean-компонента B, а bean-компонент B - от bean-компонента A.



Еще одна полезная статья, объясняющая, как возникают круговые зависимости: octoperf.com/blog/2018/02/15/spring-circular-dependencies
Джером Л.

Ответы:


42

Как говорили другие ответы, Spring просто заботится об этом, создавая bean-компоненты и вводя их по мере необходимости.

Одним из последствий является то, что инъекция bean-компонента / установка свойств могут происходить в порядке, отличном от того, что, по-видимому, подразумевают ваши XML-файлы проводки. Поэтому вам нужно быть осторожным, чтобы ваши установщики свойств не выполняли инициализацию, основанную на уже вызванных других установщиках. Способ справиться с этим - объявить bean-компоненты как реализующие InitializingBeanинтерфейс. Это требует от вас реализации afterPropertiesSet()метода, и именно здесь вы выполняете критическую инициализацию. (Я также включаю код, чтобы проверить, действительно ли установлены важные свойства.)


76

Справочное руководство Spring объясняет , как круговые зависимости разрешаются. Сначала создаются экземпляры бобов, а затем вводятся друг в друга.

Рассмотрим этот класс:

package mypackage;

public class A {

    public A() {
        System.out.println("Creating instance of A");
    }

    private B b;

    public void setB(B b) {
        System.out.println("Setting property b of A instance");
        this.b = b;
    }

}

И аналогичный класс B:

package mypackage;

public class B {

    public B() {
        System.out.println("Creating instance of B");
    }

    private A a;

    public void setA(A a) {
        System.out.println("Setting property a of B instance");
        this.a = a;
    }

}

Если бы у вас был этот файл конфигурации:

<bean id="a" class="mypackage.A">
    <property name="b" ref="b" />
</bean>

<bean id="b" class="mypackage.B">
    <property name="a" ref="a" />
</bean>

При создании контекста с использованием этой конфигурации вы увидите следующий результат:

Creating instance of A
Creating instance of B
Setting property a of B instance
Setting property b of A instance

Обратите внимание, что когда aвводится в b, aеще не полностью инициализирован.


26
Вот почему Spring требует конструктор без аргументов ;-)
Крис Томпсон,

15
Нет, если вы используете аргументы конструктора в определениях ваших компонентов! (Но в этом случае у вас не может быть круговой зависимости.)
Ричард Фирн,

1
@Richard Fearn Ваш пост посвящен объяснению проблемы, а не предложению решения?
gstackoverflow

4
Если вы попытаетесь использовать внедрение конструктора, org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
X. Wo

19

В кодовой базе, с которой я работаю (1 миллион + строк кода), у нас была проблема с длительным временем запуска, около 60 секунд. Мы получали 12000+ FactoryBeanNotInitializedException .

Я установил условную точку останова в AbstractBeanFactory # doGetBean

catch (BeansException ex) {
   // Explicitly remove instance from singleton cache: It might have been put there
   // eagerly by the creation process, to allow for circular reference resolution.
   // Also remove any beans that received a temporary reference to the bean.
   destroySingleton(beanName);
   throw ex;
}

где это destroySingleton(beanName)я напечатал исключение с условным кодом точки останова:

   System.out.println(ex);
   return false;

По-видимому, это происходит, когда FactoryBean участвуют в циклическом графе зависимостей. Мы решили это, реализовав ApplicationContextAware и InitializingBean и вручную внедрив bean-компоненты.

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class A implements ApplicationContextAware, InitializingBean{

    private B cyclicDepenency;
    private ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        ctx = applicationContext;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        cyclicDepenency = ctx.getBean(B.class);
    }

    public void useCyclicDependency()
    {
        cyclicDepenency.doSomething();
    }
}

Это сократило время запуска примерно до 15 секунд.

Поэтому не всегда предполагайте, что весна может помочь вам решить эти проблемы.

По этой причине я бы рекомендовал отключить разрешение циклических зависимостей с помощью AbstractRefreshableApplicationContext # setAllowCircularReferences (false), чтобы предотвратить многие проблемы в будущем.


3
Интересная рекомендация. Моя встречная рекомендация заключается в том, чтобы делать это только в том случае, если вы подозреваете, что циклические ссылки вызывают проблемы с производительностью. (Было бы стыдно сломать что-то, что не нужно было ломать, пытаясь исправить проблему, которая не нуждалась в исправлении.)
Стивен К.

2
Это скользкий спуск к аду обслуживания, чтобы разрешить циклические зависимости, перепроектирование вашей архитектуры из циклических зависимостей может быть действительно сложным, как это было в нашем случае. Для нас это примерно означало, что во время запуска мы получили в два раза больше подключений к базе данных, чем фабрика сеансов была задействована в циклической зависимости. В других сценариях могли произойти гораздо более катастрофические вещи из-за того, что компонент был создан более 12000 раз. Конечно, вы должны написать свои beans так, чтобы они поддерживали их уничтожение, но зачем вообще допускать такое поведение?
jontejj

@jontejj, ты заслуживаешь печенья
serprime

14

Проблема ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(B b) { this.b = b };
}


Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
 }

// Вызвано: org.springframework.beans.factory.BeanCurrentlyInCreationException: Ошибка при создании bean-компонента с именем 'A': Запрошенный bean-компонент в настоящее время находится в создании: есть ли неразрешимая циклическая ссылка?

Решение 1 ->

Class A {
    private B b; 
    public A( ) {  };
    //getter-setter for B b
}

Class B {
    private A a;
    public B( ) {  };
    //getter-setter for A a
}

Решение 2 ->

Class A {
    private final B b; // must initialize in ctor/instance block
    public A(@Lazy B b) { this.b = b };
}

Class B {
    private final A a; // must initialize in ctor/instance block
    public B(A a) { this.a = a };
}

12

Он просто делает это. Он создает aиb и вставляет каждый в другой (используя свои методы установки).

В чем проблема?


9
@javaguy: Нет, не будет.
skaffman

@skaffman только способ с использованием метода after propertiesSet?
gstackoverflow

6

Из ссылки Spring :

Как правило, вы можете доверять Spring в правильных поступках. Он обнаруживает проблемы конфигурации, такие как ссылки на несуществующие bean-компоненты и циклические зависимости, во время загрузки контейнера. Spring устанавливает свойства и разрешает зависимости как можно позже, когда компонент действительно создается.


6

Контейнер Spring может разрешать циклические зависимости на основе Setter, но выдает исключение времени выполнения BeanCurrentlyInCreationException в случае циклических зависимостей на основе конструктора. В случае циклической зависимости на основе Setter контейнер IOC обрабатывает ее иначе, чем в типичном сценарии, в котором он полностью настраивал бы взаимодействующий bean-компонент перед его внедрением. Например, если Bean A зависит от Bean B, а Bean B - от Bean C, контейнер полностью инициализирует C, прежде чем вводить его в B, и как только B полностью инициализирован, он вводится в A. Но в случае циклической зависимости один одного боба передается другому до его полной инициализации.


5

Скажем, A зависит от B, тогда Spring сначала создаст экземпляр A, затем B, затем установит свойства для B, а затем установит B в A.

Но что, если B также зависит от A?

Насколько я понимаю: Spring только что обнаружил, что A был построен (конструктор выполнен), но не полностью инициализирован (не все инъекции выполнены), ну, он подумал, что все в порядке, это терпимо, что A не полностью инициализирован, просто установите это not- на данный момент полностью инициализированы экземпляры A в B. После того, как B полностью инициализирован, он был установлен в A, и, наконец, A был полностью инициализирован.

Другими словами, он просто заранее выставляет A на B.

Для зависимостей через конструктор Sprint просто выбрасывает BeanCurrentlyInCreationException, чтобы разрешить это исключение, установите для lazy-init значение true для bean-компонента, который зависит от других через конструктор-arg.


простое и одно из лучших объяснений.
Sritam Jagadev

5

Это ясно объяснено здесь . Спасибо Евгению Паращеву.

Циклическая зависимость - это запах дизайна, либо исправьте его, либо используйте @Lazy для зависимости, которая вызывает проблему, чтобы ее обойти.


3

Если вы обычно используете инъекцию конструктора и не хотите переключаться на инъекцию свойств, тогда инъекция метода поиска Spring позволит одному bean-компоненту лениво искать другой и, следовательно, обходить циклическую зависимость. См. Здесь: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161


3

Внедрение конструктора завершается ошибкой, если между Spring beans существует круговая зависимость. Таким образом, в этом случае внедрение Setter помогает решить проблему.

По сути, внедрение конструктора полезно для обязательных зависимостей, для необязательных зависимостей лучше использовать внедрение через сеттер, потому что мы можем выполнять повторное внедрение.


0

Если два bean-компонента зависят друг от друга, мы не должны использовать инъекцию конструктора в обоих определениях bean-компонента. Вместо этого мы должны использовать внедрение сеттера в любой из bean-компонентов. (конечно, мы можем использовать инъекцию установщика в обоих определениях bean, но инъекции конструктора в обоих выбрасывают 'BeanCurrentlyInCreationException'

См Спринг документ в « https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource »

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.