Zirkuläre Abhängigkeit im Frühjahr

Antworten:

42

Wie die anderen Antworten bereits sagten, kümmert sich Spring nur darum, erstellt die Bohnen und injiziert sie nach Bedarf.

Eine der Konsequenzen ist, dass die Bean-Injektion / Eigenschaftseinstellung möglicherweise in einer anderen Reihenfolge erfolgt, als es Ihre XML-Verdrahtungsdateien zu implizieren scheinen. Sie müssen also darauf achten, dass Ihre Eigenschaftssetter keine Initialisierung durchführen, die davon abhängt, dass andere Setter bereits aufgerufen wurden. Der Weg, um damit umzugehen, besteht darin, Beans als Implementierung der InitializingBeanSchnittstelle zu deklarieren . Dazu müssen Sie die afterPropertiesSet()Methode implementieren , und hier führen Sie die kritische Initialisierung durch. (Ich füge auch Code hinzu, um zu überprüfen, ob wichtige Eigenschaften tatsächlich festgelegt wurden.)

Stephen C.
quelle
76

Im Spring-Referenzhandbuch wird erläutert, wie zirkuläre Abhängigkeiten aufgelöst werden. Die Bohnen werden zuerst instanziiert und dann ineinander injiziert.

Betrachten Sie diese Klasse:

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

}

Und eine ähnliche Klasse 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;
    }

}

Wenn Sie dann diese Konfigurationsdatei hatten:

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

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

Beim Erstellen eines Kontexts mit dieser Konfiguration wird die folgende Ausgabe angezeigt:

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

Beachten Sie, dass , wenn ain injiziert wird b, aist noch nicht vollständig initialisiert.

Richard Fearn
quelle
26
Aus diesem Grund benötigt Spring einen Konstruktor ohne Argumente ;-)
Chris Thompson
15
Nicht, wenn Sie Konstruktorargumente in Ihren Bean-Definitionen verwenden! (Aber in diesem Fall können Sie keine zirkuläre Abhängigkeit haben.)
Richard Fearn
1
@Richard Fearn Geht es in Ihrem Beitrag eher um die Erklärung des Problems als um die Bereitstellung von Lösungen?
gstackoverflow
4
Wenn Sie versuchen, Konstruktorinjektion zu verwenden, lautet die Fehlermeldungorg.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

In der Codebasis, mit der ich arbeite (1 Million + Codezeilen), hatten wir ein Problem mit langen Startzeiten von etwa 60 Sekunden. Wir haben mehr als 12000 FactoryBeanNotInitializedException erhalten .

Ich habe in AbstractBeanFactory # doGetBean einen bedingten Haltepunkt festgelegt

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

Wo es funktioniert, habe destroySingleton(beanName)ich die Ausnahme mit dem bedingten Haltepunktcode gedruckt:

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

Anscheinend geschieht dies, wenn FactoryBeans an einem zyklischen Abhängigkeitsgraphen beteiligt sind. Wir haben es gelöst, indem wir ApplicationContextAware und InitializingBean implementiert und die Beans manuell injiziert haben.

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

Dadurch wurde die Startzeit auf ca. 15 Sekunden verkürzt.

Gehen Sie also nicht immer davon aus, dass der Frühling diese Referenzen gut für Sie lösen kann.

Aus diesem Grund würde ich empfehlen, die zyklische Abhängigkeitsauflösung mit AbstractRefreshableApplicationContext # setAllowCircularReferences (false) zu deaktivieren , um viele zukünftige Probleme zu vermeiden.

jontejj
quelle
3
Interessante Empfehlung. Meine Gegenempfehlung wäre, dies nur zu tun, wenn Sie den Verdacht haben, dass Zirkelverweise ein Leistungsproblem verursachen. (Es wäre eine Schande, etwas zu zerbrechen, das nicht zerbrochen werden musste, indem versucht wurde, ein Problem zu beheben, das nicht
Stephen C
2
Es ist ein rutschiger Hang zur Wartungshölle, um zirkuläre Abhängigkeiten zuzulassen. Das Neugestalten Ihrer Architektur aus kreisförmigen Abhängigkeiten kann sozusagen in unserem Fall sehr schwierig sein. Für uns bedeutete dies ungefähr, dass wir beim Start doppelt so viele Datenbankverbindungen hatten, wie die Sessionfactory an der zirkulären Abhängigkeit beteiligt war. In anderen Szenarien hätten viel katastrophalere Dinge passieren können, weil die Bohne mehr als 12000 Mal instanziiert wurde. Sicher sollten Sie Ihre Bohnen so schreiben, dass sie die Zerstörung unterstützen, aber warum sollten Sie dieses Verhalten überhaupt zulassen?
Jontejj
@jontejj, Sie verdienen einen Keks
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 };
 }

// Auslöser: org.springframework.beans.factory.BeanCurrentlyInCreationException: Fehler beim Erstellen der Bean mit dem Namen 'A': Angeforderte Bean wird gerade erstellt: Gibt es einen nicht auflösbaren Zirkelverweis?

Lösung 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
}

Lösung 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 };
}
Akshay N. Shelke
quelle
12

Es macht es einfach. Es instanziiert aund bund injiziert sich gegenseitig (unter Verwendung ihrer Setter-Methoden).

Was ist das Problem?

Skaffman
quelle
9
@javaguy: Nein, das wird es nicht.
Skaffman
@skaffman nur so mit nach propertiesSet Methode Verwendung richtig?
gstackoverflow
6

Aus der Frühlingsreferenz :

Sie können sich im Allgemeinen darauf verlassen, dass Spring das Richtige tut. Es erkennt Konfigurationsprobleme wie Verweise auf nicht vorhandene Beans und zirkuläre Abhängigkeiten beim Laden des Containers. Spring legt Eigenschaften fest und löst Abhängigkeiten so spät wie möglich auf, wenn die Bean tatsächlich erstellt wird.

Earldouglas
quelle
6

Der Spring-Container kann Setter-basierte zirkuläre Abhängigkeiten auflösen, gibt jedoch bei Konstruktor-basierten zirkulären Abhängigkeiten eine Laufzeitausnahme BeanCurrentlyInCreationException aus. Im Falle einer Setter-basierten zirkulären Abhängigkeit behandelt der IOC-Container diese anders als in einem typischen Szenario, in dem die kollaborierende Bean vor dem Injizieren vollständig konfiguriert wird. Wenn beispielsweise Bean A eine Abhängigkeit von Bean B und Bean B von Bean C hat, initialisiert der Container C vollständig, bevor er es in B injiziert, und sobald B vollständig initialisiert ist, wird er in A injiziert. Im Falle einer zirkulären Abhängigkeit jedoch eine der Bohnen wird in die andere injiziert, bevor sie vollständig initialisiert ist.

Saurav S.
quelle
5

Angenommen, A hängt von B ab, dann instanziiert Spring zuerst A, dann B, setzt dann die Eigenschaften für B und setzt B dann auf A.

Aber was ist, wenn B auch von A abhängt?

Mein Verständnis ist: Spring hat gerade festgestellt, dass A konstruiert (Konstruktor ausgeführt), aber nicht vollständig initialisiert wurde (nicht alle Injektionen durchgeführt). Nun, es dachte, es ist in Ordnung, es ist tolerierbar, dass A nicht vollständig initialisiert ist. Setzen Sie dies einfach nicht. vorerst vollständig initialisierte A-Instanzen in B. Nachdem B vollständig initialisiert wurde, wurde es auf A gesetzt, und schließlich wurde A jetzt vollständig initiiert.

Mit anderen Worten, es setzt A vorab B aus.

Für Abhängigkeiten über den Konstruktor löst Sprint einfach BeanCurrentlyInCreationException aus. Um diese Ausnahme zu beheben, setzen Sie lazy-init für die Bean, die über den Konstruktor-Arg-Weg von anderen abhängt, auf true.

Leise
quelle
einfach und eine der besten Erklärungen.
Sritam Jagadev
5

Seine deutlich erklärt hier . Vielen Dank an Eugen Paraschiv.

Zirkuläre Abhängigkeit ist ein Entwurfsgeruch. Beheben Sie ihn entweder oder verwenden Sie @Lazy für die Abhängigkeit, die dazu führt, dass das Problem umgangen wird.

Wer bin ich
quelle
3

Wenn Sie im Allgemeinen die Konstruktorinjektion verwenden und nicht zur Eigenschaftsinjektion wechseln möchten, lässt die Spring- Lookup-Methodeninjektion eine Bean die andere träge nachschlagen und umgeht so die zyklische Abhängigkeit. Siehe hier: http://docs.spring.io/spring/docs/1.2.9/reference/beans.html#d0e1161

Barclar
quelle
3

Die Konstruktorinjektion schlägt fehl, wenn zwischen Spring Beans eine kreisförmige Abhängigkeit besteht. In diesem Fall hilft die Setter-Injektion, das Problem zu beheben.

Grundsätzlich ist die Konstruktorinjektion für obligatorische Abhängigkeiten nützlich, für optionale Abhängigkeiten ist es besser, die Setter-Injektion zu verwenden, da wir eine erneute Injektion durchführen können.

Premraj
quelle
0

Wenn zwei Beans voneinander abhängig sind, sollten wir die Konstruktorinjektion nicht in beiden Bean-Definitionen verwenden. Stattdessen müssen wir in jeder der Bohnen die Setter-Injektion verwenden. (Natürlich können wir die Setter-Injection in beiden Bean-Definitionen verwenden, aber Konstruktor-Injections in beiden werfen 'BeanCurrentlyInCreationException'.

Weitere Informationen finden Sie im Spring-Dokument unter " https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#resources-resource ".

Srikant M.
quelle