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

100

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

fastcodejava
источник
2
хорошая статья baeldung.com/circular-dependencies-in-spring
gstackoverflow
Еще одна полезная статья, объясняющая, как возникают круговые зависимости: 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 устанавливает свойства и разрешает зависимости как можно позже, когда компонент действительно создается.

Earldouglas
источник
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

Barclar
источник
3

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

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

Премрадж
источник
0

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

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

Срикант М
источник