Кругова залежність навесні


100

Як весна вирішує це: квасоля A залежить від квасолі B, а квасоля B - квасоля А.



Ще одна корисна стаття, що пояснює, як відбуваються кругові залежності: octoperf.com/blog/2018/02/15/spring-circular-dependitions
Ієронім L

Відповіді:


42

Як сказано в інших відповідях, Весна просто дбає про це, створюючи боби і впорскуючи їх у міру необхідності.

Одним із наслідків є те, що інжекція / властивість бобів може відбуватися в іншому порядку, ніж те, що, здавалося б, передбачає ваші XML-файли електропроводки. Тому вам слід бути обережними, щоб ваші налаштування властивостей не зробили ініціалізацію, яка покладається на інші вже встановлені дзвінки. Спосіб вирішити це - оголосити боби як реалізацію 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
Ні, якщо ви використовуєте конструкторські аргументи у своїх визначеннях bean! (Але в такому випадку ви не можете мати кругову залежність.)
Річард Фірн

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?
інжекцією,

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 s залучаються до циклічного графіка залежності. Ми вирішили це, застосувавши ApplicationContextAware та InitializingBean та вручивши боби вручну.

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 (помилковим), щоб запобігти багатьом майбутнім проблемам.


3
Цікава рекомендація. Моя зустрічна рекомендація буде робити це лише в тому випадку, якщо ви підозрюєте, що кругові посилання викликають проблеми з продуктивністю. (Соромно було б зламати щось, чого не потрібно було зламати, намагаючись виправити проблему, яка не потребувала виправлення.)
Stephen C

2
Це слизький схил до пекла технічного обслуговування, щоб дозволити кругові залежності, перепроектування вашої архітектури з кругових залежностей може бути справді складним, як це було у нашому випадку. Це приблизно означало для нас те, що ми отримали вдвічі більше підключень до бази даних під час запуску, оскільки сеансфабрика була залучена до кругової залежності. В інших сценаріях могло трапитися набагато жахливіше, тому що боб був інстанційований 12000+ разів. Впевнені, що ви повинні написати свої боби, щоб вони підтримували їх знищення, але навіщо в першу чергу допускати таку поведінку?
jontejj

@jontejj, ти заслуговуєш на cookie
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": Запитаний боб наразі створюється: Чи існує нерозв'язна кругова посилання?

Рішення 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 єдиний спосіб з використанням методу afterSetSet належним чином?
gstackoverflow

6

З весняної довідки :

Ви, як правило, можете довіряти Весні, щоб зробити все правильно. Він виявляє проблеми з конфігурацією, такі як посилання на неіснуючі боби і кругові залежності, під час завантаження контейнера. Весна встановлює властивості та вирішує залежності як можна пізніше, коли боб фактично створений.


6

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


5

Скажіть, A залежить від B, тоді Spring спочатку інстанціює A, потім B, потім встановить властивості для B, потім встановить B на A.

Але що робити, якщо B також залежить від A?

Я розумію: Весна щойно виявила, що A побудований (конструктор виконаний), але не повністю ініціалізований (не всі ін'єкції зроблено), ну, він вважав, що це нормально, допустимо, що A не повністю ініціалізовано, просто встановіть це не- повністю ініціалізовані екземпляри A на даний момент. Після того, як B повністю ініціалізований, він був встановлений у A, і нарешті, A був повністю ініційований зараз.

Іншими словами, воно просто заздалегідь виставляє А до В.

Для залежностей через конструктор Sprint просто кинемо BeanCurrentlyInCreationException, щоб вирішити цей виняток, встановивши lazy-init на істинне для bean, що залежить від інших, через конструктор-arg спосіб.


просте і одне з найкращих пояснень.
Срітам Ягадев

5

Його чітко пояснено тут . Дякуємо Євгену Паращів.

Кругова залежність - це дизайнерський запах, або виправити його, або використовувати @Lazy для залежності, яка спричиняє проблему для подолання.


3

Якщо ви, як правило, використовуєте конструктор-ін'єкцію та не хочете переходити до властивостей-ін'єкцій, тоді метод пошуку Spring -ін'єкція дозволить одному бобі лінько шукати інший і, отже, обходити циклічну залежність. Дивіться тут: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161


3

Інжекція конструктора не вдається, коли між пружинистими бобами існує кругова залежність. Тож у цьому випадку ми ін’єкція сетера допомагає вирішити проблему.

В основному, інжекція конструктора корисна для обов`язкових залежностей, для необов'язкових залежностей краще використовувати ін'єкцію Setter, оскільки ми можемо зробити повторну ін'єкцію.


0

Якщо дві квасолі залежать одна від одної, тоді ми не повинні використовувати ін'єкцію Constructor в обох визначеннях квасолі. Натомість нам доводиться використовувати ін'єкцію сетера в будь-яку з квасолі. (звичайно, ми можемо використовувати інжектор сетерів n обох визначень боба, але конструкторські ін'єкції в обох кидках "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.