Zależność cykliczna na wiosnę


Odpowiedzi:


42

Jak powiedziały inne odpowiedzi, Spring po prostu się tym zajmuje, tworząc ziarna i wstrzykując je w razie potrzeby.

Jedną z konsekwencji jest to, że wstrzykiwanie / ustawianie właściwości fasoli może zachodzić w innej kolejności, niż sugerowałyby to pliki łączników XML. Musisz więc uważać, aby ustawienia ustawiające właściwości nie przeprowadzały inicjalizacji, która polega na wywołaniu innych metod ustawiających. Sposobem na rozwiązanie tego problemu jest zadeklarowanie fasoli jako implementującej InitializingBeaninterfejs. Wymaga to zaimplementowania afterPropertiesSet()metody i tutaj wykonujesz krytyczną inicjalizację. (Dołączam również kod, aby sprawdzić, czy ważne właściwości zostały faktycznie ustawione).


76

Reference Manual Wiosna wyjaśnia, w jaki sposób wzajemnie od siebie zależnych są rozwiązane. Ziarna są najpierw tworzone, a następnie wstrzykiwane do siebie nawzajem.

Rozważ tę klasę:

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;
    }

}

I podobna klasa 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;
    }

}

Jeśli wtedy miałeś ten plik konfiguracyjny:

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

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

Podczas tworzenia kontekstu przy użyciu tej konfiguracji zobaczysz następujące dane wyjściowe:

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

Zauważ, że kiedy ajest wstrzykiwany b, anie jest jeszcze w pełni zainicjowany.


26
Dlatego Spring wymaga konstruktora bez argumentów ;-)
Chris Thompson,

15
Nie, jeśli używasz argumentów konstruktora w definicjach komponentów bean! (Ale w takim przypadku nie możesz mieć zależności cyklicznej.)
Richard Fearn,

1
@Richard Fearn Czy Twój post dotyczy wyjaśnienia problemu, a nie podania rozwiązania?
gstackoverflow

4
Jeśli spróbujesz użyć iniekcji konstruktora, komunikat o błędzie toorg.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
X. Wo Satuk

19

W bazie kodu, z którą pracuję (1 milion + wierszy kodu) mieliśmy problem z długimi czasami uruchamiania, około 60 sekund. Byliśmy coraz 12000+ FactoryBeanNotInitializedException .

To, co zrobiłem, to ustawienie warunkowego punktu przerwania w 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;
}

gdzie to jest destroySingleton(beanName), wydrukowałem wyjątek z kodem warunkowego punktu przerwania:

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

Najwyraźniej dzieje się tak, gdy FactoryBean są zaangażowane w cykliczny wykres zależności. Rozwiązaliśmy to, wdrażając ApplicationContextAware i InitializingBean oraz ręcznie wprowadzając ziarna.

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();
    }
}

Skróciło to czas uruchamiania do około 15 sekund.

Dlatego nie zawsze zakładaj, że wiosna może być dobra w rozwiązywaniu tych odniesień.

Z tego powodu polecam wyłączenie cyklicznego rozwiązywania zależności za pomocą AbstractRefreshableApplicationContext # setAllowCircularReferences (false), aby zapobiec wielu przyszłym problemom.


3
Ciekawa rekomendacja. Moją przeciwną rekomendacją byłoby zrobienie tego tylko wtedy, gdy podejrzewasz, że odwołania cykliczne powodują problem z wydajnością. (Szkoda byłoby zepsuć coś, czego nie trzeba było zepsuć, próbując naprawić problem, który nie wymagał naprawy.)
Stephen C

2
Jest to śliskie zejście do piekła konserwacji, aby umożliwić zależności cykliczne, przeprojektowanie architektury z zależności cyklicznych może być naprawdę trudne, tak jak to było w naszym przypadku. Z grubsza oznaczało to dla nas to, że podczas uruchamiania otrzymaliśmy dwa razy więcej połączeń z bazą danych, niż fabryka sesji była zaangażowana w zależność cykliczną. W innych scenariuszach mogło dojść do znacznie bardziej katastrofalnych rzeczy z powodu tworzenia instancji fasoli ponad 12000 razy. Jasne, powinieneś napisać swoje ziarna tak, aby wspierały ich niszczenie, ale po co w ogóle pozwalać na takie zachowanie?
jontejj

@jontejj, zasługujesz na ciastko
serprime

14

Problem ->

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 };
 }

// Spowodowane przez: org.springframework.beans.factory.BeanCurrentlyInCreationException: Błąd podczas tworzenia komponentu bean o nazwie „A”: Żądany komponent bean jest obecnie tworzony: czy istnieje nierozwiązywalne cykliczne odwołanie?

Rozwiązanie 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
}

Rozwiązanie 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

Po prostu to robi. Tworzy instancje ai bi wstrzykuje je do siebie (używając ich metod ustawiających).

Jaki jest problem?


9
@javaguy: Nie, nie będzie.
skaffman

@skaffman jedyny sposób z właściwym użyciem metody after propertiesSet?
gstackoverflow

6

Ze źródła wiosennego :

Generalnie możesz ufać Springowi, że postąpi właściwie. Wykrywa problemy z konfiguracją, takie jak odwołania do nieistniejących komponentów bean i zależności cyklicznych, w czasie ładowania kontenera. Spring ustawia właściwości i rozwiązuje zależności tak późno, jak to możliwe, kiedy fasola jest faktycznie tworzona.


6

Kontener Spring jest w stanie rozwiązać zależności cykliczne oparte na narzędziu Setter, ale daje wyjątek czasu wykonywania BeanCurrentlyInCreationException w przypadku zależności cyklicznych opartych na konstruktorze. W przypadku zależności cyklicznej opartej na programie Setter, kontener IOC obsługuje go inaczej niż w typowym scenariuszu, w którym w pełni skonfigurowałby współpracujący komponent bean przed wstrzyknięciem. Na przykład, jeśli Bean A ma zależność od Bean B i Bean B od Bean C, kontener w pełni inicjuje C przed wstrzyknięciem go do B i po pełnym zainicjowaniu B jest wstrzykiwany do A. Ale w przypadku zależności cyklicznej, jeden ziaren jest wstrzykiwana do drugiej, zanim zostanie w pełni zainicjowana.


5

Powiedzmy, że A zależy od B, a następnie Spring najpierw utworzy wystąpienie A, potem B, a następnie ustawi właściwości dla B, a następnie ustawi B na A.

Ale co, jeśli B również zależy od A?

Rozumiem: Spring właśnie odkrył, że A został skonstruowany (wykonano konstruktor), ale nie został w pełni zainicjowany (nie wykonano wszystkich wstrzyknięć), cóż, pomyślał, jest OK, to tolerowane, że A nie jest w pełni zainicjowany, po prostu ustaw to nie- na razie w pełni zainicjalizowane instancje A w B. Po pełnym zainicjowaniu B został ustawiony na A, a ostatecznie A został teraz w pełni zainicjowany.

Innymi słowy, po prostu wystawia z wyprzedzeniem A na B.

W przypadku zależności za pośrednictwem konstruktora Sprint po prostu wyrzuć BeanCurrentlyInCreationException, aby rozwiązać ten wyjątek, ustaw lazy-init na true dla komponentu bean, który zależy od innych, poprzez sposób konstruktora-arg.


proste i jedno z najlepszych wyjaśnień.
Sritam Jagadev

5

Jest to jasno wyjaśnione tutaj . Podziękowania dla Eugena Paraschiva.

Zależność cykliczna jest zapachem projektu, napraw ją lub użyj @Lazy dla zależności, która powoduje problem w jej obejściu.



3

Wstrzyknięcie konstruktora kończy się niepowodzeniem, gdy między fasolkami wiosennymi występuje zależność kołowa. Więc w tym przypadku my Setter wtrysk pomaga rozwiązać problem.

Zasadniczo iniekcja konstruktora jest przydatna w przypadku zależności obowiązkowych, w przypadku zależności opcjonalnych lepiej jest używać iniekcji settera, ponieważ możemy wykonać ponowne wstrzyknięcie.


0

Jeśli dwie fasole są od siebie zależne, nie powinniśmy używać wtrysku konstruktora w obu definicjach fasoli. Zamiast tego musimy użyć zastrzyku ustawiającego w dowolnej fasoli. (oczywiście możemy użyć iniekcji setter w obu definicjach bean, ale iniekcje konstruktora w obu rzutach 'BeanCurrentlyInCreationException'

Zapoznaj się z dokumentem Spring pod adresemhttps://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.